您的位置:首页 > 运维架构

opencv输出中文字符,并控制字符大小

2017-07-28 12:33 1016 查看
转载请注明出处:http://blog.csdn.net/yiqiudream/article/details/76216433

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

需要解决的问题:

1. 怎么用opencv 输出中文字符?

2. 怎么控制输出字符的大小?

环境:

vs2010 + opencv2.4.9 + freetype2.8

本文所使用的文件打包后下载地址:http://download.csdn.net/detail/yiqiudream/9913839

首先找到了这篇博文 http://blog.csdn.net/zmdsjtu/article/details/53133223 并下载了博主的文件,包含以下内容:



直接新建了工程,把文件放进去,编译,发现缺少文件而无法编译。按照博客方法操作,但是我不知道怎么在我的vs2010中一键安装freetype,所以放弃安装。

后来参考这些博文:
http://blog.csdn.net/huixingshao/article/details/43563853
http://blog.csdn.net/wanrenwangxuejing/article/details/39895595
http://blog.csdn.net/xufeng0991/article/details/40735651

发现需要自己下载 freetype库
我的下载地址:http://download.savannah.gnu.org/releases/freetype/ 
版本是 freetype-2.8.tar.gz 。解压后得到 ft28 文件夹。
该版本的API手册:FreeType-2.8 API Reference

编译:
找到路径:***\ft28\freetype-2.8\builds\windows\vc2010\freetype.sln   
用vs2010打开该工程文件,分别在 debug 和 release 环境下编译,得到两个文件:
freetype28.lib  和 freetype28d.lib
这两个文件的位置在:***\ft28\freetype-2.8\objs\vc2010\Win32\

新建一个工程,将freetype库添加到工程:
添加包含目录:右键属性->VC++目录->包含目录->添加

***\ft28\freetype-2.8\include
***\ft28\freetype-2.8\include\opencv
***\ft28\freetype-2.8\include\opencv2

添加库目录:右键属性->VC++目录->库目录->添加
***\ft28\freetype-2.8\objs\vc2010\Win32
添加附加依赖项:freetype28.lib   freetype28d.lib

在新建工程目录下,添加一个字体文件,该文件从C:\Windows\Fonts\文件夹下的.tff文件中选择一个,复制到工程目录下。我添加了simhei.tff

下载 CvxText.h   CvxText.cpp文件,放在工程scan1文件夹下,并添加进工程中来。

(我忘记当时下载的链接了,这里有一个:http://download.csdn.net/detail/jacky_ponder/9680241

最终工程结构:



测试:
在main_CvxText.cpp中输入:

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <math.h>
#include <iostream>
#include "CvxText.h"

using namespace cv;
using namespace std;

int main()
{
Mat src( Size(800, 600), CV_8UC3, Scalar(220,220,220));
imwrite("src.bmp", src);

//格式转换
IplImage oldImg = IplImage(src);
IplImage *oldImg1 = &oldImg;

//指定字体
CvxText text("simhei.ttf");   //黑体

//打印第一行字
const char* msg = "黑体字(50,50)(100,0.5,0.1,0)";
float p=1;  //暂时不考虑透明度
double my_val[4] = {100, 0.5, 0.1, 0};  //这里第一个参数并不能设置字体大小,只是设置了字符之间的距离。
CvScalar *my_size = (CvScalar *)(my_val);
text.setFont(NULL, my_size, NULL, 0);
text.putText(oldImg1, msg, cvPoint(50, 100), CV_RGB(0,0,0));
//cvSaveImage("oldImg1.bmp", oldImg1);

//打印第二行字
const char* msg2 = "黑体字(50,50)(80,0.5,0.1,0)";
float p2=1;  //暂时不考虑透明度
double my_val_2[4] = {80, 0.5, 0.1, 0};  //这里第一个参数并不能设置字体大小,只是设置了字符之间的距离。
CvScalar *my_size2 = (CvScalar *)(my_val_2);
text.setFont(NULL, my_size2, NULL, 0);
text.putText(oldImg1, msg2, cvPoint(50, 200), CV_RGB(0,0,0));
//cvSaveImage("oldImg1.bmp", oldImg1);

//打印第三行字
const char* msg3 = "黑体字(50,50)(60,0.5,0.1,0)";
float p3=1;  //暂时不考虑透明度
double my_val_3[4] = {60, 0.5, 0.1, 0};  //这里第一个参数并不能设置字体大小,只是设置了字符之间的距离。
CvScalar *my_size3 = (CvScalar *)(my_val_3);
text.setFont(NULL, my_size3, NULL, 0);
text.putText(oldImg1, msg3, cvPoint(50, 300), CV_RGB(0,0,0));
cvSaveImage("oldImg1.bmp", oldImg1);

//格式转换
Mat dst = Mat(oldImg1);
imwrite("dst.bmp", dst);

while(1)
{
int key = waitKey();                 // 等待按键按下
if( (key & 255) == 27)            // 如果按下了Esc键
break;
}

return 0;

}

由于我需要在opencv2的环境下用到打印中文字符的功能,所以进行了格式转换。不需要的可以直接用IplImage载入图片。

输出图片效果:



存在问题:

这三行字之间的差别就在于 text.setFont(NULL,my_size,NULL, 0); 第二个参数my_size,其中my_size[0]
分别设置成了100、80、60.  在该函数定义中这个参数控制字体大小,但实际情况是,字体大小并没有改变,只是字符之间的距离变化了。那要如何控制字符大小呢? 如何控制字符间距呢?

查看源码:

在工程scan1中查看 CvxText.h 及 CvxText.cpp中的函数,比如FT_Set_Pixel_Sizes()函数,右键选择“转到定义”,结果找不到该定义。猜测原因是我自己建的这个工程并没有把所有需要的源文件都组织起来。
解决办法:

找到最初编译 ft28 的工程,地址是***\ft28\freetype-2.8\builds\windows\vc2010\freetype.sln, 打开该.sln文件。并且把CvxText.h及 CvxText.cpp 复制到解决方案文件夹下,并添加进工程freetype中。从这里打开这两个文件,选中某个函数再右键选择“转到定义”就可以找到该函数的定义了。

freetype工程下有这么多文件:



先看设置字体的函数setFont()

/**
* 设置字体。目前有些参数尚不支持。
*
* \param font        字体类型, 目前不支持
* \param size        字体大小/空白比例/间隔比例/旋转角度
* \param underline   下画线
* \param diaphaneity 透明度
*
* \sa getFont, restoreFont
*/

void setFont(int *type,
CvScalar *size=NULL, bool *underline=NULL, float *diaphaneity=NULL);
从注释上看,第二个参数 CvScalar *size  负责设置字体大小/空白比例/间隔比例/旋转角度。但从前面的测试结果看,size[0]并不能改变字体大小。
从参考的文献来看,FT_Set_Pixel_Sizes()函数是用来设置字体大小的。
FT_Set_Pixel_Sizes()  函数只有在 restoreFont()函数中被调用一次,其他地方都没有用到。

restoreFont() 函数的目的是设置一个默认的字符参数,如果我在setFont()函数中的参数都写Null 也没关系,会使用这里默认的参数。

restoreFont() 函数只在 构造函数 CvxText::CvxText()中被调用,其他地方没有出现。也就是说在我新建对象的时候(main_CvxText.cpp中:     CvxText text("simhei.ttf");   //黑体),内部完成的步骤有:打开字体库,创建一个字体,设置字体输出参数(默认参数),设置C语言的字符集环境。    其中,在执行 restoreFont() 的时候,就执行了FT_Set_Pixel_Sizes()
。也是整个程序中唯一执行FT_Set_Pixel_Sizes()的一次。也就是说新建对象的时候,字体大小已经确定了,后面再也没有调用该函数去修改字体大小,所以最终输出结果的字体大小是固定的,不能修改。

CvxText::CvxText(const char *freeType)
{
assert(freeType != NULL);

// 打开字库文件, 创建一个字体

if(FT_Init_FreeType(&m_library)) throw;   //打开字库文件
if(FT_New_Face(m_library, freeType, 0, &m_face)) throw;    //创建一个字体

// 设置字体输出参数

restoreFont();

// 设置C语言的字符集环境  (C语言默认采用的是ASCII字符集,转换成Unicode)

setlocale(LC_ALL, "");
}

// 恢复原始的字体设置

void CvxText::restoreFont()
{
m_fontType = 0;            // 字体类型(不支持)

m_fontSize.val[0] = 50;      // 字体大小
m_fontSize.val[1] = 0.5;   // 空白字符大小比例
m_fontSize.val[2] = 0.1;   // 间隔大小比例
m_fontSize.val[3] = 0;      // 旋转角度(不支持)

m_fontUnderline   = false;   // 下画线(不支持)

m_fontDiaphaneity = 1.0;   // 色彩比例(可产生透明效果)

// 设置字符大小

FT_Set_Pixel_Sizes(m_face, (int)m_fontSize.val[0], 0);
}
FT_Set_Pixel_Sizes() 函数有点复杂,还要追踪到下一个函数,没有搞清楚细节,目前只需要直到它是用来设置字体大小的。

因此,想要修改字体大小,就得自己写一个接口,调用 FT_Set_Pixel_Sizes() 函数去修改字体大小。

在CvxText.h文件中添加 setMySize 函数的声明:

// 我发现要改变字体的大小,必须是在FT_Set_Pixel_Sizes()函数中设置,所以额外写一个函数来修改字体大小。
void setMySize( int a, int b);


在CvxText.cpp文件中添加setMySize 函数的定义:

// 我发现要改变字体的大小,必须是在FT_Set_Pixel_Sizes()函数中设置,所以额外写一个函数来修改字体大小。
void CvxText::setMySize( int a, int b)
{
FT_Set_Pixel_Sizes(m_face, a , b);
}
其中,参数a 和参数b分别对应字符的高和宽,两者可以分别设置(将字体压扁或者拉长);如果设置一个,另一个为0,则默认两者相等。

在 main_CvxText.cpp中每次打印字体之前添加一行: text.setMySize(100, 50);   //设置默认的字体大小

修改后的main_CvxText.cpp如下

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <math.h>
#include <iostream>
#include "CvxText.h"

using namespace cv;
using namespace std;

int main()
{
Mat src( Size(800, 600), CV_8UC3, Scalar(220,220,220));
imwrite("src.bmp", src);

//格式转换
IplImage oldImg = IplImage(src);
IplImage *oldImg1 = &oldImg;

//指定字体
CvxText text("simhei.ttf");   //黑体
text.setMySize(80, 80);   //设置默认的字体大小
//打印第一行字
const char* msg = "黑体字(80,80)(80,0.5,0.1,0)";
float p=1;  //暂时不考虑透明度
double my_val[4] = {100, 0.5, 0.1, 0};  //这里第一个参数并不能设置字体大小,只是设置了字符之间的距离。
CvScalar *my_size = (CvScalar *)(my_val);
text.setFont(NULL, my_size, NULL, 0);
text.putText(oldImg1, msg, cvPoint(50, 100), CV_RGB(0,0,0));
//cvSaveImage("oldImg1.bmp", oldImg1);

//打印第二行字
text.setMySize(60, 60);   //设置默认的字体大小
const char* msg2 = "黑体字(60,60)(80,0.5,0.1,0)";
float p2=1;  //暂时不考虑透明度
double my_val_2[4] = {80, 0.5, 0.1, 0};  //这里第一个参数并不能设置字体大小,只是设置了字符之间的距离。
CvScalar *my_size2 = (CvScalar *)(my_val_2);
text.setFont(NULL, my_size2, NULL, 0);
text.putText(oldImg1, msg2, cvPoint(50, 200), CV_RGB(0,0,0));
//cvSaveImage("oldImg1.bmp", oldImg1);

//打印第三行字
text.setMySize(40, 40);   //设置默认的字体大小
const char* msg3 = "黑体字(40,40)(80,0.5,0.1,0)";
float p3=1;  //暂时不考虑透明度
double my_val_3[4] = {60, 0.5, 0.1, 0};  //这里第一个参数并不能设置字体大小,只是设置了字符之间的距离。
CvScalar *my_size3 = (CvScalar *)(my_val_3);
text.setFont(NULL, my_size3, NULL, 0);
text.putText(oldImg1, msg3, cvPoint(50, 300), CV_RGB(0,0,0));
cvSaveImage("oldImg1.bmp", oldImg1);

//格式转换
Mat dst = Mat(oldImg1);
//imwrite("dst.bmp", dst);

while(1)
{
int key = waitKey();                 // 等待按键按下
if( (key & 255) == 27)            // 如果按下了Esc键
break;
}

return 0;
}
输出结果图:



那么 setFont() 函数的作用是什么呢?

void CvxText::setFont(int *type, CvScalar *size, bool *underline, float *diaphaneity)
{
// 参数合法性检查

if(type)
{
if(type >= 0) m_fontType = *type;
}
if(size)
{
m_fontSize.val[0] = fabs(size->val[0]);
m_fontSize.val[1] = fabs(size->val[1]);
m_fontSize.val[2] = fabs(size->val[2]);
m_fontSize.val[3] = fabs(size->val[3]);
}
if(underline)
{
m_fontUnderline   = *underline;
}
if(diaphaneity)
{
m_fontDiaphaneity = *diaphaneity;
}

}


在 setFont() 函数中设置了 m_fontSize.val[4] 的四个值。m_fontSize在打印字符的时候用到,下一个字符从哪里开始打印就根据这个参数计算得到。
// 输出当前字符, 更新m_pos位置

void CvxText::putWChar(IplImage *img, wchar_t wc, CvPoint &pos, CvScalar color)
{
// 根据unicode生成字体的二值位图

FT_UInt glyph_index = FT_Get_Char_Index(m_face, wc);  //根据字符的编码得到字符的索引位置
FT_Load_Glyph(m_face, glyph_index, FT_LOAD_DEFAULT);  //根据字符在字库中的索引得到字符轮廓
FT_Render_Glyph(m_face->glyph, FT_RENDER_MODE_MONO);  //根据字符的轮廓渲染字符的二值位图。

//

FT_GlyphSlot slot = m_face->glyph;

// 行列数

int rows = slot->bitmap.rows ;   //怎么确定的?
int cols = slot->bitmap.width ;

//

for(int i = 0; i < rows; ++i)
{
for(int j = 0; j < cols; ++j)
{
int off  = ((img->origin==0)? i: (rows-1-i))    //origin!=0的时候从最后一行开始?
* slot->bitmap.pitch + j/8;

if(slot->bitmap.buffer[off] & (0xC0 >> (j%8)))
{
int r = (img->origin==0)? pos.y - (rows-1-i): pos.y + i;;
int c = pos.x + j;

if(r >= 0 && r < img->height
&& c >= 0 && c < img->width)
{
CvScalar scalar = cvGet2D(img, r, c);

// 进行色彩融合

float p = m_fontDiaphaneity;
for(int k = 0; k < 4; ++k)
{
scalar.val[k] = scalar.val[k]*(1-p) + color.val[k]*p;
}

cvSet2D(img, r, c, scalar);
}
}
} // end for
} // end for

// 修改下一个字的输出位置

double space = m_fontSize.val[0]*m_fontSize.val[1];
double sep   = m_fontSize.val[0]*m_fontSize.val[2];

pos.x += (int)((cols? cols: space) + sep);
}

看最下面的几行,
double space = m_fontSize.val[0]*m_fontSize.val[1];
double sep   =m_fontSize.val[0]*m_fontSize.val[2];
pos.x += (int)((cols? cols: space) + sep);
由 space 和 sep 计算出pos.x  也就是计算出下一个要打印字符的位置。space是由 m_fontSize.val[0]和 m_fontSize.val[1] 相乘得到的,sep 是由 m_fontSize.val[0]和 m_fontSize.val[2] 相乘得到的。所以我理解为,m_fontSize.val[0]是一个基数,m_fontSize.val[1]和 m_fontSize.val[2] 是比例,合起来控制字符间距。

测试一下。修改main_CvxText.cpp中 my_size 的参数(my_val[4])。text.setMySize(40,40)不变。

//指定字体
CvxText text("simhei.ttf");   //黑体
text.setMySize(40, 40);   //设置默认的字体大小
//打印第一行字
const char* msg = "黑体字(40,40)(150,0.5,0.1,0)";
float p=1;  //暂时不考虑透明度
double my_val[4] = {200, 0.5, 0.1, 0};  //这里第一个参数并不能设置字体大小,只是设置了字符之间的距离。
CvScalar *my_size = (CvScalar *)(my_val);
text.setFont(NULL, my_size, NULL, 0);
text.putText(oldImg1, msg, cvPoint(50, 100), CV_RGB(0,0,0));
//cvSaveImage("oldImg1.bmp", oldImg1);

//打印第二行字
text.setMySize(40, 40);   //设置默认的字体大小
const char* msg2 = "黑体字(40,40)(100,0.5,0.1,0)";
float p2=1;  //暂时不考虑透明度
double my_val_2[4] = {100, 0.5, 0.1, 0};  //这里第一个参数并不能设置字体大小,只是设置了字符之间的距离。
CvScalar *my_size2 = (CvScalar *)(my_val_2);
text.setFont(NULL, my_size2, NULL, 0);
text.putText(oldImg1, msg2, cvPoint(50, 200), CV_RGB(0,0,0));
//cvSaveImage("oldImg1.bmp", oldImg1);

//打印第三行字
text.setMySize(40, 40);   //设置默认的字体大小
const char* msg3 = "黑体字(40,40)(100,0.1,0.05,0)";
float p3=1;  //暂时不考虑透明度
double my_val_3[4] = {100, 0.1, 0.05, 0};  //这里第一个参数并不能设置字体大小,只是设置了字符之间的距离。
CvScalar *my_size3 = (CvScalar *)(my_val_3);
text.setFont(NULL, my_size3, NULL, 0);
text.putText(oldImg1, msg3, cvPoint(50, 300), CV_RGB(0,0,0));
cvSaveImage("oldImg1.bmp", oldImg1);
输出结果图:



因为text.setMySize(40,40)不变,所以字体大小不变,my_val[4]中的前三个参数的改变使得字符间距改变了。

CvxText.h 去掉注释整理一下,看得清楚一点。
#ifndef OPENCV_CVX_TEXT_2007_08_31_H
#define OPENCV_CVX_TEXT_2007_08_31_H

#include <ft2build.h>
#include FT_FREETYPE_H

#include <cv.h>
#include <highgui.h>

class CvxText
{
// 禁止copy
CvxText& operator=(const CvxText&);

public:
CvxText(const char *freeType);   //构造函数
virtual ~CvxText();     //析构函数

void getFont(int *type,
CvScalar *size=NULL, bool *underline=NULL, float *diaphaneity=NULL);    //获得字体参数

void setFont(int *type,
CvScalar *size=NULL, bool *underline=NULL, float *diaphaneity=NULL);   //设置字体参数

void restoreFont();    //设置默认的参数,包括字体、下划线等

void setMySize( int a, int b);    //为了修改字体大小,自定义的一个函数

int putText(IplImage *img, const char    *text, CvPoint pos);  //打印字符重载函数,里面都调用了putWchar
int putText(IplImage *img, const wchar_t *text, CvPoint pos);
int putText(IplImage *img, const char    *text, CvPoint pos, CvScalar color);
int putText(IplImage *img, const wchar_t *text, CvPoint pos, CvScalar color);

private:
void putWChar(IplImage *img, wchar_t wc, CvPoint &pos, CvScalar color);  //执行打印字符的具体函数

private:

FT_Library   m_library;   // 字库
FT_Face      m_face;      // 字体

int         m_fontType;   //字体格式
CvScalar   m_fontSize;    //字体尺寸
bool      m_fontUnderline;  //下划线
float      m_fontDiaphaneity;  //透明度
};

#endif


////////////////////////////////////////////////
本文还欠考虑的问题:m_fontSize.val[4] 值如何设置才能够得到想要的字体大小、间隔?目前只知道调大一点,调小一点,不知道具体的数值和像素点的准确关系。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息