OpenCV图片文本聚合 2021.3.5
1、目标需求
原始图片origin.jpg,大小为 531 x 461 像素
需要添加以下文本到图片的正下方后,共同组成一张图片。
名称:1号水质传感器
经度:81.624
纬度:44.227
最终希望得到如下Word中这样的整张图片。
2、环境搭建 (Windows 10 64位操作系统下)
这里使用的环境配置主要包括:VS 2010、OpenCV 2.4.9、C++
2.1 安装Visual Studio 2010(VS 2015更好)
2.2 安装OpenCV(使用预编译库)
OpenCV官网下点击上方Library下的Releases就可以查看发行版的各版本OpenCV,其中包括文档、源码、Windows系统下的安装文件、iOS系统下的安装文件以及Android系统下的安装文件和Java开发文档,比如选择了OpenCV 2.4.9,选择其中的Windows后就可以下载所需的OpenCV-2.4.9.exe,同时可以点击下方的Tutorials后可以根据官方文档指导来配置OpenCV环境。
下载好opencv-2.4.9.exe后就可以双击进行安装,安装会弹出一个窗口,选择自己提取到的目录D:\Program Files (x86)\OpenCV 2.4.9\ 后确定即可,安装完成后文件夹opencv下包含build文件夹和sources文件夹,里面的文件内容如下图所示:
vc10 对应 Visual Studio 2010
vc11 对应 Visual Studio 2012
vc12 对应 Visual Studio 2013
vc14 对应 Visual Studio 2015
vc与Visual Studio版本的对应关系如上图所示,然后设置系统的环境变量,由于这里使用的是Visual Studio 2010,所以应该选择vc10,因此需要将D:\Program Files (x86)\OpenCV 2.4.9\opencv\build\x64\vc10\bin和D:\Program Files (x86)\OpenCV 2.4.9\opencv\build\x86\vc10\bin添加到Path中,因为bin文件夹就是编译生成的二进制binary文件夹,里面包含了dll文件,其中封装了opencv生成的核心函数,放到Path系统环境变量中便于其他使用OpenCV函数的项目来通过路径寻找到对应的dll文件并在运行时进行调用。
非必须:当然如果C盘空间较多,也可以尝试将所需要的D:\Program Files (x86)\OpenCV 2.4.9\opencv\build\x86\vc10\bin下所有的dll文件复制到C:\Windows\System32目录下,这个操作本质上和设置Path环境变量的作用效果相同,都是为了在项目运行时找到所依赖的dll。(如果项目运行提示找不到dll的话,可以尝试这样做,32位C++项目将OpenCV下对应VS版本的x86下bin中全部dll复制到C:\Windows\System32目录下;若为64位项目则应将OpenCV下对应VS版本的x64下bin中全部dll复制到C:\Windows\SysWOW64目录下)
3、在Visual Studio中利用OpenCV来构建C++应用程序实现需求
3.1 VS新建C++Win32控制台应用程序并配置OpenCV目录
可以参考官方教程,打开Visual Studio 2010后,选择文件->新建后会打开新建项目窗口,在新建项目窗口中选择Visual C++后点击中间的Win 32控制台应用程序,在下方输入项目名称CVProject,并选择位置后点击确定(值得注意的是:x86对应32位应用程序,x64对应64位应用程序,然后相应地设置lib库目录和include包含目录即附加依赖项.lib并添加对应值bin目录到环境变量Path)
弹出Win 32应用程序向导窗口,点击下一步
应用程序类型选择控制台应用程序,附加选项选择空项目后点击确定即可。
然后在解决方案资源管理器中找到项目下的源文件,右键点击添加后选择新建项,会弹出添加新项窗口
在添加新项窗口中选择C++文件(.cpp)后,在下方名称后的文本框中输入main.cpp后再点击添加
现在在项目CVProject上右键选择属性后弹出CVProject属性也窗口,找到VC++目录下的包含目录和库目录,包含目录即需要添加OpenCV的include目录(D:\Program Files (x86)\OpenCV 2.4.9\opencv\build\include、
D:\Program Files (x86)\OpenCV 2.4.9\opencv\build\include\opencv和
D:\Program Files (x86)\OpenCV 2.4.9\opencv\build\include\opencv2),而库目录需要添加OpenCV的lib目录(D:\Program Files (x86)\OpenCV 2.4.9\opencv\build\x86\vc10\lib)
同时在链接器的输入下找到附加依赖项,在附加依赖项中输入如下lib后点击应用并确定即可(如果项目配置为Debug,选择opencv_ts249d.lib;如果为Release,选择opencv_ts249.lib,带d的为Debug配置项目所需要的依赖库)
opencv_objdetect249.lib opencv_ts249.lib opencv_video249.lib opencv_nonfree249.lib opencv_ocl249.lib opencv_photo249.lib opencv_stitching249.lib opencv_superres249.lib opencv_videostab249.lib opencv_calib3d249.lib opencv_contrib249.lib opencv_core249.lib opencv_features2d249.lib opencv_flann249.lib opencv_gpu249.lib opencv_highgui249.lib opencv_imgproc249.lib opencv_legacy249.lib opencv_ml249.lib
3.2 实现代码并运行
最后将下面的C++代码复制到main.cpp中,在项目上右键点击生成后没有报错就启动本地Windows调试器运行项目。
#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void GetStringSize(HDC hDC, const char* str, int* w, int* h)
{
SIZE size;
GetTextExtentPoint32A(hDC, str, strlen(str), &size);
if (w != 0) *w = size.cx;
if (h != 0) *h = size.cy;
}
void MyPutText(Mat &dst, const char* str, Point org, Scalar color, int fontSize, const char* fn, bool italic, bool underline)
{
CV_Assert(dst.data != 0 && (dst.channels() == 1 || dst.channels() == 3));
int x, y, r, b;
if (org.x > dst.cols || org.y > dst.rows) return;
x = org.x < 0 ? -org.x : 0;
y = org.y < 0 ? -org.y : 0;
LOGFONTA lf;
lf.lfHeight = -fontSize;
lf.lfWidth = 0;
lf.lfEscapement = 0;
lf.lfOrientation = 0;
lf.lfWeight = 5;
lf.lfItalic = italic; //斜体
lf.lfUnderline = underline; //下划线
lf.lfStrikeOut = 0;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfOutPrecision = 0;
lf.lfClipPrecision = 0;
lf.lfQuality = PROOF_QUALITY;
lf.lfPitchAndFamily = 0;
strcpy_s(lf.lfFaceName, fn);
HFONT hf = CreateFontIndirectA(&lf);
HDC hDC = CreateCompatibleDC(0);
HFONT hOldFont = (HFONT)SelectObject(hDC, hf);
int strBaseW = 0, strBaseH = 0;
int singleRow = 0;
char buf[1 << 12];
strcpy_s(buf, str);
char *bufT[1 << 12]; // 这个用于分隔字符串后剩余的字符,可能会超出。
//处理多行
{
int nnh = 0;
int cw, ch;
const char* ln = strtok_s(buf, "\n",bufT);
while (ln != 0)
{
GetStringSize(hDC, ln, &cw, &ch);
strBaseW = max(strBaseW, cw);
strBaseH = max(strBaseH, ch);
ln = strtok_s(0, "\n",bufT);
nnh++;
}
singleRow = strBaseH;
strBaseH *= nnh;
}
if (org.x + strBaseW < 0 || org.y + strBaseH < 0)
{
SelectObject(hDC, hOldFont);
DeleteObject(hf);
DeleteObject(hDC);
return;
}
r = org.x + strBaseW > dst.cols ? dst.cols - org.x - 1 : strBaseW - 1;
b = org.y + strBaseH > dst.rows ? dst.rows - org.y - 1 : strBaseH - 1;
org.x = org.x < 0 ? 0 : org.x;
org.y = org.y < 0 ? 0 : org.y;
BITMAPINFO bmp = { 0 };
BITMAPINFOHEADER& bih = bmp.bmiHeader;
int strDrawLineStep = strBaseW * 3 % 4 == 0 ? strBaseW * 3 : (strBaseW * 3 + 4 - ((strBaseW * 3) % 4));
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = strBaseW;
bih.biHeight = strBaseH;
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = BI_RGB;
bih.biSizeImage = strBaseH * strDrawLineStep;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
void* pDibData = 0;
HBITMAP hBmp = CreateDIBSection(hDC, &bmp, DIB_RGB_COLORS, &pDibData, 0, 0);
CV_Assert(pDibData != 0);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hDC, hBmp);
//color.val[2], color.val[1], color.val[0]
SetTextColor(hDC, RGB(255, 255, 255));
SetBkColor(hDC, 0);
//SetStretchBltMode(hDC, COLORONCOLOR);
strcpy_s(buf, str);
const char* ln = strtok_s(buf, "\n",bufT);
int outTextY = 0;
while (ln != 0)
{
TextOutA(hDC, 0, outTextY, ln, strlen(ln));
outTextY += singleRow;
ln = strtok_s(0, "\n",bufT);
}
uchar* dstData = (uchar*)dst.data;
int dstStep = dst.step / sizeof(dstData[0]);
unsigned char* pImg = (unsigned char*)dst.data + org.x * dst.channels() + org.y * dstStep;
unsigned char* pStr = (unsigned char*)pDibData + x * 3;
for (int tty = y; tty <= b; ++tty)
{
unsigned char* subImg = pImg + (tty - y) * dstStep;
unsigned char* subStr = pStr + (strBaseH - tty - 1) * strDrawLineStep;
for (int ttx = x; ttx <= r; ++ttx)
{
for (int n = 0; n < dst.channels(); ++n){
double vtxt = subStr[n] / 255.0;
int cvv = vtxt * color.val[n] + (1 - vtxt) * subImg[n];
subImg[n] = cvv > 255 ? 255 : (cvv < 0 ? 0 : cvv);
}
subStr += 3;
subImg += dst.channels();
}
}
SelectObject(hDC, hOldBmp);
SelectObject(hDC, hOldFont);
DeleteObject(hf);
DeleteObject(hBmp);
DeleteDC(hDC);
}
void ProduceNewPicturewithText(std::string filename,const char* rowtext[])
{
cv::Mat img1 = imread(filename);
std::cout << img1.rows << "," << img1.cols << std::endl;//输出第一张图片的高度和宽度
cv::Mat img2(100, img1.cols, CV_8UC3, Scalar(255,255,255));//初始化图像
int flag = 0;//1表示顺时针旋转90度,0表示逆时针旋转90度
MyPutText(img2, rowtext[0], Point(img2.cols / 3 + 10,img2.rows / 5), CV_RGB(0, 0, 0), 15, "黑体", false, false);
MyPutText(img2, rowtext[1], Point(img2.cols / 3 + 10,img2.rows / 5 + 30), CV_RGB(0, 0, 0), 15, "黑体", false, false);
MyPutText(img2, rowtext[2], Point(img2.cols / 3 + 10,img2.rows / 5 + 60), CV_RGB(0, 0, 0), 15, "黑体", false, false);//给图片添加三行文字
cv::Mat temp1,temp2,img_left,img_right;
cv::transpose(img1, temp1); cv::flip(temp1, img_left,flag);//将第一张图片逆时针旋转90度
cv::transpose(img2, temp2); cv::flip(temp2, img_right,flag);//将第二章图片逆时针旋转90度
cv::imshow("第一张图片逆时针旋转90度", img_left);
cv::imshow("第二张图片逆时针旋转90度", img_right);
cv::Mat img_merge,outImg_left, outImg_right;
Size size(img_left.cols + img_right.cols, MAX(img_left.rows, img_right.rows));
img_merge.create(size, CV_MAKETYPE(img_left.depth(), 3));
img_merge = Scalar::all(0);
outImg_left = img_merge(Rect(0, 0, img_left.cols, img_left.rows));
outImg_right = img_merge(Rect(img_left.cols, 0, img_right.cols, img_right.rows));
if(img_left.type() == CV_8U)
{
cvtColor(img_left, outImg_left, CV_GRAY2BGR);
}
else
{
img_left.copyTo(outImg_left);
}
if(img_right.type() == CV_8U)
{
cvtColor(img_right, outImg_right, CV_GRAY2BGR);
}
else
{
img_right.copyTo(outImg_right);
}
cv::Mat middleImg,resultImg;
cv::transpose(img_merge, middleImg); cv::flip(middleImg,resultImg,flag+1);//最后将图片顺时针旋转90度
cv::imshow("Merge Result", resultImg);
cv::imwrite("E:\\Merge Result.jpg",resultImg);
}
void main()
{
std::string filename = "E:\\origin.png";
const char* rowtext[3];
//char* rowtext1 = "名称:1号水质传感器";
std::string rowtext1 = "名称:";
std::string rowtext2 = "经度:";
std::string rowtext3 = "纬度:";
rowtext1+="2号水质传感器";
rowtext[0] = rowtext1.c_str();//字符串赋值给Const char *
rowtext2+="81.624";
rowtext[1] = rowtext2.c_str();
rowtext3+="44.227";
rowtext[2] = rowtext3.c_str();
ProduceNewPicturewithText(filename,rowtext);
std::cout << "Success!" << std::endl;
cv::waitKey(0);
//txt文件里如果有文本,需要保存成ANSI格式,才能读取txt中的文本
system("pause");
}
3.3 运行结果
程序运行后会在E盘保存生成的图片文本聚合文件,打开E盘进行查看并用照片编辑器打开,如下图所示,目标达成,还算满足需求。