一、在工程中配置potracelib静态库
新建一个工程,在添加的头文件处加入编译好的静态链接库,
并通过Project–>右键Build options–>Search directories:(头文件的位置)
以及Project–>Build options–>Linker settings:(加载库文件)设好置 编译器工程的库和头文件的搜索路径,现在就可以在新工程中使用编译好 的potracelib静态库了。
二、代码实现
1、为了批量生成矢量图,先编写满足从文件夹中读取所有的bmp文件的方法。
//GetAllFiles方法用来获取指定路径下所有的文件名
void GetAllFiles( string path, vector<string>& files)
{
//文件句柄
long hFile = 0;
//定义_finddata_t结构体fileinfo来存储文件信息
struct _finddata_t fileinfo;
//定义字符串变量p用来保存文件所在的目录结构和文件名
string p;
//首先调用_findfirst方法查找第一个文件,若成功则用返回的句柄调用_findnext方法继续查找其它的文件
if((hFile = _findfirst(p.assign(path).append("\\*").c_str(),&fileinfo)) != -1)
{
do
{
//如果该文件的属性是文件夹,则继续遍历该文件里面的内容
if((fileinfo.attrib & _A_SUBDIR))
{
//由于在进入一个子目录时,最先搜索到的前两个文件(夹)是"."(当前目录)和".."(上一层目录),因此需要将这两种情况排除掉
if(strcmp(fileinfo.name,".") != 0
&& strcmp(fileinfo.name,"..") != 0)
{
//将该文件名添加到字符串p后面
files.push_back(p.assign(path).append("\\").append(fileinfo.name) );
//递归调用GetAllFiles()方法继续遍历该文件夹下的子文件
GetAllFiles( p.assign(path).append("\\").append(fileinfo.name), files );
}
}
//否则,说明该文件已经是最内层的文件,
//调用push_back方法将该文件名添加到字符串p后面
else
{
files.push_back(p.assign(path).append("\\").append(fileinfo.name) );
}
//如果查找下一个文件成功,则继续执行循环遍历下一个文件
}
while(_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
方法解释:
该方法有两个参数,第一个为表示指定路径的字符串(string类型),第二个参数为文件夹与文件名称存储变量(vector类型)。
GetAllFiles方法的执行过程中,首先定义一个_finddata_t结构体来存储文件的各种信息,包括文件属性的存储位置、文件创建时间、文件最后一次被访问的时间、文件最后一次被修改的时间、文件的大小、文件名等。接下来调用_findfirst、_findnext和_fineclose方法将硬盘文件的文件信息存储到_finddata_t结构体所表示的内存空间里。
_findfirs方法用来查找第一个文件,如果查找成功,则返回一个long类型的查找用的句柄(即,一个唯一的编号;这个句柄将在_findnext函数中被使用);如果查找失败,则返回-1。_findfirs方法中包含两个参数:第一个参数是filespec,用来标明文件的字符串,可支持通配符(比如:*.c,则表示当前文件夹下的所有后缀为C的文件);第二个参数是fileinfo,作为存放文件信息的结构体的指针。_findfirs方法成功查找到第一个文件后,将该文件的信息放入这个结构体中。
_findnext方法用来继续查找下一个文件,如果查找成功,则返回0,否则返回-1。_findnext方法中包含两个参数:第一个参数是handle,用来保存_findfirst函数返回的句柄;第二个参数是fileinfo,用做文件信息结构体的指针。_findnext方法找到文件后,将该文的件信息放入这个结构体中。
_findclose方法在查找结束后关闭文件句柄,如果关闭成功,则返回0,否则返回-1。_findclose方法包含一个参数handle,用来保存_findfirst函数返回的句柄。
文件查找具体过程包括:首先调用_findfirst方法查找第一个文件,若成功则用hFile保存返回的句柄,调用_findnext方法继续查找下一个文件。采用递归的深度优先搜索遍历每个文件里面的内容:如果该文件的属性是文件夹,则继续调用GetAllFiles方法遍历该文件里面的内容;否则,该文件已经是最内层的文件,直接调用push_back方法将该文件名添加到字符串p后面。最后,文件查找结束,调用_fineclose方法关闭文件句柄hFile。
2、然后结合上文介绍的bmp文件的边缘检测以及图像融合步骤(硬笔字体需要这两步,而软笔字体可以省略)生成新的可以使用的OpenCV:mat。
//读取源图像并检查图像是否读取成功
Mat srcImage = imread(bmp_picture[j]);
if (!srcImage.data)
{
cout << "读取图片错误,请重新输入正确路径!\n";
system("pause");
return -1;
}
imshow("【源图像】", srcImage);
//灰度转换
Mat srcGray;
cvtColor(srcImage, srcGray, CV_RGB2GRAY);
imshow("【灰度图】", srcGray);
//初始化相关变量
//初始化自适应阈值参数
Mat dstImage;
const int maxVal = 255;
int blockSize = 3; //取值3、5、7....等
int constValue = 10;
int adaptiveMethod = 1;
int thresholdType = 0;
/*
自适应阈值算法
0:ADAPTIVE_THRESH_MEAN_C
1:ADAPTIVE_THRESH_GAUSSIAN_C
--------------------------------------
阈值类型
0:THRESH_BINARY
1:THRESH_BINARY_INV
*/
// 图像自适应阈值操作
// adaptiveThreshold(srcGray, dstImage, maxVal, adaptiveMethod, thresholdType, blockSize, constValue);
//
// imshow("【自适应阈值】", dstImage);
//
//scharr边缘检测
Mat image_scharr ;
Scharr(srcGray, image_scharr, CV_8UC1,1, 0);
imshow("scharr 边缘检测", image_scharr);
//利用addWeighted()函数对两幅图像进行融合
Mat newImage(srcGray.size(), srcGray.type());
//最后融合效果显示在灰度图像上。
addWeighted(srcGray, 0.5, image_scharr, 0.5, 0., newImage);
/*
若不想毁坏原始srcGray图像,也可建立一个与原始图像类型尺寸一样的新图像,将融合后的图像保存到上面。
建立方法:
Mat newImage(srcGray.size(), srcGray.type()); //newImage与srcGray类型尺寸相同
*/
// namedWindow("图像1与图像2融合效果图");
imshow("图像1与图像2融合效果图", newImage);
waitKey();
3.接下来,通过potrace其他源码文件,找到可以由OpenCV的mat格式生成可以被potracelib使用的bmp文件格式的方法bitmapFromMat(const cv::Mat& image, int threshold).以及一些其他的辅助函数。如下。
//计算给定dy和h的位图数据区域所需的大小(以字节为单位),假设h >= 0。
//如果大小不适合ptrdiff_t类型,则返回-1。
ptrdiff_t getsize(int dy, int h)
{
ptrdiff_t size;
if(dy < 0)
{
dy = -dy;
}
size = (ptrdiff_t)dy * (ptrdiff_t)h * (ptrdiff_t)BM_WORDSIZE;
/* check for overflow error */
if(size < 0 || (h != 0 && dy != 0 && size / h / dy != BM_WORDSIZE))
{
return -1;
}
return size;
}
//返回初始化为0的新位图。NULL错误为error。假设 w, h >= 0.
potrace_bitmap_t *bm_new(int w, int h)
{
potrace_bitmap_t *bm;
int dy = w == 0 ? 0 : (w - 1) / BM_WORDBITS + 1;
ptrdiff_t size;
size = getsize(dy, h);
if(size < 0)
{
errno = ENOMEM;
return NULL;
}
if(size == 0)
{
size = 1; //确保calloc()不返回NULL
bm = (potrace_bitmap_t *)malloc(sizeof(potrace_bitmap_t));
if(!bm)
{
return NULL;
}
bm->w = w;
bm->h = h;
bm->dy = dy;
bm->map = (potrace_word *)calloc(1, size);
if(!bm->map)
{
free(bm);
return NULL;
}
return bm;
}
}
//释放位图空间
void bm_free(potrace_bitmap_t *bm)
{
if(bm != NULL)
{
free(bm->map);
}
free(bm);
}
// 仅适用于CV_8UC1二进制或灰度图像
potrace_bitmap_t* bitmapFromMat(const cv::Mat& image, int threshold)
{
potrace_bitmap_t *bitmap = bm_new(image.cols, image.rows);
int pi = 0;
for(int row = 0; row < image.rows; ++row)
{
const uchar* ptr = image.ptr<uchar>(image.rows - 1 - row);
for(int col = 0; col < image.cols; ++col)
{
if(ptr[col] > threshold)
BM_PUT(bitmap, col, row, 0);
else
BM_PUT(bitmap, col, row, 1);
}
}
return bitmap;
}
需要注意的不是所有的Mat都可以生成potrace算法可以利用的bitmap,而是只有CV_8UC1二进制格式的Mat或灰度图像才可以,所以在第一步处理bmp图片时检测边缘或者图片融合后得到的Mat格式需要设置为CV_8UC1或者直接利用原图像的灰度图像。生成potrace算法可以利用的bitmap之后,按照设置跟踪参数、跟踪位图、将矢量图形写入文件的步骤,利用potracelib中的方法加以实现。这里参考了potracelib_demo.c源文件中的代码。
//生成potrace可以利用的bmp格式
bm = bitmapFromMat(srcGray, 0);
//从默认值开始设置跟踪参数
param = potrace_param_default();
if (!param) {
fprintf(stderr, "Error allocating parameters: %s\n", strerror(errno));
return 1;
}
param->turdsize = 0;
//跟踪位图
st = potrace_trace(param, bm);
if (!st || st->status != POTRACE_STATUS_OK) {
fprintf(stderr, "Error tracing bitmap: %s\n", strerror(errno));
return 1;
}
cout<<"跟踪位图已完毕"<<endl;
//打开文件,写入矢量图形内容
string svg_file = bmp_picture[j].substr(0,bmp_picture[j].find('.'))+".eps";
FILE* file = fopen(svg_file,"w");
if(!file)
return -1;
//输出向量数据,例如作为一个基本的EPS文件
fprintf(file,"%%!PS-Adobe-3.0 EPSF-3.0\n");
fprintf(file,"%%%%BoundingBox: 0 0 %d %d\n", bm->w, bm->h);
fprintf(file,"gsave\n");
//画出每一个曲线
p = st->plist;
while (p != NULL) {
n = p->curve.n;
tag = p->curve.tag;
c = p->curve.c;
fprintf(file,"%f %f moveto\n", c[n-1][2].x, c[n-1][2].y);
for (i=0; i<n; i++) {
switch (tag[i]) {
case POTRACE_CORNER:
fprintf(file,"%f %f lineto\n", c[i][1].x, c[i][1].y);
fprintf(file,"%f %f lineto\n", c[i][2].x, c[i][2].y);
break;
case POTRACE_CURVETO:
fprintf(file,"%f %f %f %f %f %f curveto\n",
c[i][0].x, c[i][0].y,
c[i][1].x, c[i][1].y,
c[i][2].x, c[i][2].y);
break;
}
}
//在一组正向路径及其反向子路径的末尾填充
if (p->next == NULL || p->next->sign == '+') {
fprintf(file,"0 setgray fill\n");
}
p = p->next;
}
fprintf(file,"grestore\n");
fprintf(file,"%%EOF\n");
4.最后将通过potrace算法生成的轨迹写入同名的eps文件。
//打开文件,写入矢量图形内容
string svg_file = bmp_picture[j].substr(0,bmp_picture[j].find('.'))+".eps";
FILE* file = fopen(svg_file,"w");
if(!file)
return -1;
5.结果如下