一、在工程中配置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.結果如下