天天看點

使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!

目錄

一.使用Opencv進行輪廓檢測!

二. 開始編寫代碼

三. 字元提取

四. 文字識别

一.使用Opencv進行輪廓檢測!

所需函數:

1. cvFindContours

函數功能:從二值圖像中檢索輪廓,并傳回檢測到的輪廓的個數

函數原型:

int cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour,

    int header_size = sizeof(CvContour),

    int mode = CV_RETR_LIST,

    int method = CV_CHAIN_APPROX_SIMPLE,

    CvPoint offset = cvPoint(0,0));
           

參數介紹:

CvArr* image:要檢測輪廓的圖像,必須為二值圖

CvMemStorage* storage:存儲輪廓的容器

​CvSeq** first_contour:輸出參數,用于存儲指向第一個外接輪廓。是一個連結清單,h_next用于指向下一個外接輪廓

int header_size:

header序列的尺寸.如果選擇method = CV_CHAIN_CODE, 則header_size >= sizeof(CvChain);其他,則header_size >= sizeof(CvContour)。

int mode:

CV_RETR_EXTERNAL:隻檢索最外面的輪廓;

CV_RETR_LIST:檢索所有的輪廓,并将其放入list中;

CV_RETR_CCOMP:檢索所有的輪廓,并将他們組織為兩層:頂層是各部分的外部邊界,第二層是空洞的邊界;

CV_RETR_TREE:檢索所有的輪廓,并重構嵌套輪廓的整個層次。

int method

邊緣近似方法(除了CV_RETR_RUNS使用内置的近似,其他模式均使用此設定的近似算法)。可取值如下:

CV_CHAIN_CODE:以Freeman鍊碼的方式輸出輪廓,所有其他方法輸出多邊形(頂點的序列)。

CV_CHAIN_APPROX_NONE:将所有的連碼點,轉換成點。

CV_CHAIN_APPROX_SIMPLE:壓縮水準的、垂直的和斜的部分,也就是,函數隻保留他們的終點部分。

CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:使用the flavors of Teh-Chin chain近似算法

的一種。

CV_LINK_RUNS:通過連接配接水準段的1,使用完全不同的邊緣提取算法。使用CV_RETR_LIST檢索模式能使用此方法。

CvPoint offset

偏移量,用于移動所有輪廓點。當輪廓是從圖像的ROI提取的,并且需要在整個圖像中分析時,這個參數将很有用。

​
           

2.cvThreshold

函數功能:對單通道數組應用固定門檻值操作。該函數的典型應用是對灰階圖像進行門檻值操作得到二值圖像。

函數原型:

void cvThreshold( const CvArr* src,

                CvArr* dst,

                double threshold,

                double max_value,

                int threshold_type );
           

參數介紹:

src:原始數組 (單通道 , 8-bit of 32-bit 浮點數)。

dst:輸出數組,必須與 src 的類型一緻,或者為 8-bit。

threshold:門檻值

max_value:使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值。

threshold_type:門檻值類型

threshold_type=CV_THRESH_BINARY:如果 src(x,y)>threshold ,dst(x,y) = max_value; 否則,dst(x,y)=0;

threshold_type=CV_THRESH_BINARY_INV:如果 src(x,y)>threshold,dst(x,y) = 0; 否則,dst(x,y) = max_value.

threshold_type=CV_THRESH_TRUNC:如果 src(x,y)>threshold,dst(x,y) = max_value; 否則dst(x,y) = src(x,y).

threshold_type=CV_THRESH_TOZERO:如果src(x,y)>threshold,dst(x,y) = src(x,y) ; 否則 dst(x,y) = 0。

threshold_type=CV_THRESH_TOZERO_INV:如果 src(x,y)>threshold,dst(x,y) = 0 ; 否則dst(x,y) = src(x,y).
           

3. cvCreateMemStorage

函數功能:用來建立一個記憶體存儲器,來統一管理各種動态對象的記憶體。

函數原型:

CvMemStorage*  cvCreateMemStorage( int block_size = 0);
           

參數介紹:

int block_size = 0:對應記憶體器中每個記憶體塊的大小,為0時記憶體塊預設大小為64k。
           

傳回值:傳回一個新建立的記憶體存儲器指針。

4. cvBoundingRect

函數功能:計算點集的最外面(up-right)矩形邊界。

函數原型:

CvRect  cvBoundingRect( CvArr* points, int update = 0);
           

參數介紹:

CvArr* points:二級點矩陣


int update:

更新辨別。

下面是輪廓類型和辨別的一些可能組合:

update=0, contour ~ CvContour*: 不計算矩形邊界,但直接由輪廓頭的 rect 域得到。

update=1, contour ~ CvContour*: 計算矩形邊界,而且将結果寫入到輪廓頭的 rect 域中 header。

update=0, contour ~ CvSeq* or CvMat*: 計算并傳回邊界矩形。

update=1, contour ~ CvSeq* or CvMat*: 産生運作錯誤 (runtime error is raised)。

函數 cvBoundingRect 傳回二維點集的最外面 (up-right)矩形邊界。 [1]
           

所需結構體:

1. CvMemStorage

結構體介紹:

typedef struct CvMemStorage


{


    int signature;


    CvMemBlock* bottom; /**< 第一配置設定塊。 */


    CvMemBlock* top; /**< 目前記憶體塊——堆棧的頂部。 */


    struct CvMemStorage* parent; /**< 根據需要從父塊擷取新的塊。 */


    int block_size; /**< 塊的大小。 */


    int free_space; /**< 目前塊中的剩餘空閑空間。 */


}CvMemStorage;
           

2. CvSeq

typedef struct CvSeq

{

CV_SEQUENCE_FIELDS()

} CvSeq;


#define CV_SEQUENCE_FIELDS()

int flags; /* micsellaneous flags */

int header_size; /* 序列頭的大小 */

struct CvSeq* h_prev; /* 前一個序列 */

struct CvSeq* h_next; /* 後一個序列 */

struct CvSeq* v_prev; /* 第二級前一個序列 */

struct CvSeq* v_next; /* 第二級後一個序列 */

int total; /* 元素的總個數 */

int elem_size;/* 元素的尺寸 */

char* block_max;/* 上一塊的最大塊 */

char* ptr; /* 目前寫指針 */

int delta_elems; /*序列中快的大小

(序列粒度) */

CvMemStorage* storage; /*序列的存儲位置 */

CvSeqBlock* free_blocks; /* 未配置設定的塊序列 */

CvSeqBlock* first; /* 指向第一個快序列 */
           

相關理論知識:在圖像進行中門檻值是什麼意思?

二. 開始編寫代碼

編寫代碼前需要準備一張實驗圖像!

使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!

可自行儲存到本地,png格式!

1. 加載測試圖像

//打開要識别字元的圖像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("錯誤:無法打開該圖像檔案!");

}
           

2.圖像二值化

//轉換到灰階圖

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
           

3. 在二值化圖像中尋找輪廓

//尋找輪廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
           

4. 擷取輪廓在圖像中的矩陣坐标

//根據輪廓坐标使用方框标出來

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0));

pConInner = pConInner->h_next;

}
           

5. 顯示圖像

//顯示圖像

cvNamedWindow("image", 0);

cvNamedWindow("image_gray", 0);

cvNamedWindow("img_value", 0);

cvShowImage("image", image);

cvShowImage("image_gray", img_gray);

cvShowImage("img_value", img_value);

cvWaitKey(0);
           

運作結果:

使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!

二值化圖像是很利用我們做輪廓檢測的,因為二值化的圖像中不會有其他摻雜顔色在裡面影響輪廓檢測準确!

完整代碼:

//打開要識别字元的圖像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("錯誤:無法打開該圖像檔案!");

}

//轉換到灰階圖

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

//尋找輪廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

//根據輪廓坐标使用方框标出來

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0));

pConInner = pConInner->h_next;

}

//顯示圖像

cvNamedWindow("image", 0);

cvNamedWindow("image_gray", 0);

cvNamedWindow("img_value", 0);

cvShowImage("image", image);

cvShowImage("image_gray", img_gray);

cvShowImage("img_value", img_value);

cvWaitKey(0);
           

三. 字元提取

已經将字元輪廓用矩形給标出來了,那麼接下來字元提取就較為簡單了!

我們可以複用上面的代碼,在繪制輪廓前加入:

IplImage* imgNo[9] = { NULL };
           

用于存儲分割出來的圖像

在使用cvCopy函數對其進行裁剪即可!

//根據輪廓坐标使用方框标出來

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

//cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //為了防止裁剪時出現矩陣顔色,是以将這段代碼屏蔽掉!

imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);

cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//設定源圖像ROI

cvCopy(image, imgNo[i]); //裁剪,将裁剪的字元圖像放入到imgNo數組中去

pConInner = pConInner->h_next;

}
           

//為了防止裁剪時出現矩陣顔色,是以将這段代碼屏蔽掉! imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3); cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//設定源圖像ROI cvCopy(image, imgNo[i]); //裁剪,将裁剪的字元圖像放入到imgNo數組中去 pConInner = pConInner->h_next; }

最後在将其顯示出來

char a[9];

for (int i = 0; i < 9; ++i){

sprintf(a, "%d", i);

cvShowImage(a, imgNo[i]);

}
           

運作結果:

使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!

完整代碼:

//打開要識别字元的圖像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("錯誤:無法打開該圖像檔案!");

}

//轉換到灰階圖

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

//尋找輪廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

IplImage* imgNo[9] = { NULL };

//根據輪廓坐标使用方框标出來

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

//cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //為了防止裁剪時出現矩陣顔色,是以将這段代碼屏蔽掉!

imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);

cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//設定源圖像ROI

cvCopy(image, imgNo[i]); //裁剪

pConInner = pConInner->h_next;

}

//顯示圖像

cvNamedWindow("image", 0);

cvNamedWindow("image_gray", 0);

cvNamedWindow("img_value", 0);

char a[9];

for (int i = 0; i < 9; ++i){

sprintf(a, "%d", i);

cvShowImage(a, imgNo[i]);

}

cvShowImage("image_gray", img_gray);

cvShowImage("img_value", img_value);

cvWaitKey(0);
           

四. 文字識别

在文字識别,你可以使用其他方法,本部落格中使用最簡單的直方圖比對,後續的車牌識别中,我會寫一篇通過Opencv訓練分練器,通過樣本檔案來識别提高識别率!

相關連結: 使用Opencv繪制灰階直方圖/對比

注意前提,你要有模闆圖像,這裡我把樣本圖像分享給各位!

使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!

由于模闆圖像較多,我上傳到csdn上,供需要的人下載下傳正規字元模闆,下載下傳需要兩積分,如果不想下載下傳可以使用下列圖像,然後複用上面的代碼裁剪出字元然後使用cvSeveImage函數儲存到本地即可!

使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!

然後使用下列代碼:

//打開要識别字元的圖像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("錯誤:無法打開該圖像檔案!");

}

//轉換到灰階圖

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

//尋找輪廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

IplImage* imgNo[53] = { NULL };

//根據輪廓坐标使用方框标出來

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

//cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //為了防止裁剪時出現矩陣顔色,是以将這段代碼屏蔽掉!

imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);

cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//設定源圖像ROI

cvCopy(image, imgNo[i]); //裁剪

pConInner = pConInner->h_next;

}

//顯示圖像檔案

char a[53];

for (int i = 0; i < 53; ++i){

sprintf(a, "d:\\img\\%d.jpg", i);

cvSaveImage(a, imgNo[i]);

}

cvWaitKey(0);
           

運作之後你的D盤下就會出現一個img檔案裡面包含了所有的正規字元模闆圖!

開始編寫識别代碼

這裡我們不複用上面的代碼,重新編寫一份,因為模闆比對算法有些地方有問題,是以這裡重寫一遍給大家注明一下!

注意上面的模型建議用在其他識别算法上,不建議用在模闆圖檔上,matchTemplate模闆比對函數要求尺寸一緻,這裡為了友善就不編寫尺寸變換的代碼了,隻是做一個示例,是以我就直接用

使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!

這張圖的字元樣本做模闆了,

使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!

拿出來分享給各位:

使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!
使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!
使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!
使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!
使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!
使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!
使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!
使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!
使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!

有需要可自行儲存到本地,jpg格式!

下面開始編寫代碼:

1. 定義一個結構體用于關聯圖像與字元

typedef struct p_image{

char c_name;

CvHistogram *img_zft;

}p_image;
           

2. 打開測試圖像

//打開要識别字元的圖像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("錯誤:無法打開該圖像檔案!");

}
           

3. 圖像二值化

//轉換到灰階圖

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);
           

4. 尋找輪廓

//尋找輪廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
           

5. 申請變量用于字元提取

IplImage* imgNo[9] = { NULL };
           

6. 申請變量用于轉化字元提取的直方圖

IplImage *i1[9] = { NULL };
           

7. 字元裁剪

//字元提取

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

//cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //為了防止裁剪時出現矩陣顔色,是以将這段代碼屏蔽掉!

imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);

cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//設定源圖像ROI

cvCopy(image, imgNo[i]); //裁剪

pConInner = pConInner->h_next;

}
           

8. 重新儲存與讀取

為什麼要加這段重複代碼?

原因:經過我測試,Opencv裡面的資料是沒有經過壓縮的,是以當你打開模闆圖時模闆圖格式為jpg,經過jpeg算法壓縮,當你使用直方圖比對時比對會有差異,注意加這段代碼僅針對直方圖的情況來做修改的!

其他比對方法無需增加這段代碼!

吐槽一下:直方圖比對真的是太爛了,直方圖隻能用于計算!

//jpg格式壓縮!!

//儲存與讀取

char name[256];

for (int i = 0; i < 9; ++i){

sprintf(name, "d:\\img\\%d.jpg", i);

cvSaveImage(name, imgNo[i]);

}

char b[256];

for (int i = 0; i < 9; ++i){

sprintf(b, "d:\\img\\%d.jpg", i);

imgNo[i] = cvLoadImage(b);

}
           

9. 灰階圖轉換

//灰階轉換

for (int i = 0; i < 9; ++i){

i1[i] = cvCreateImage(cvSize(imgNo[i]->width, imgNo[i]->height), 8, 1);

cvCvtColor(imgNo[i], i1[i], CV_BGR2GRAY);//CV_BGR2GRAY

}
           

10. 計算原圖的直方圖

//計算原圖的直方圖

int arr_size = 255; //定義一個變量用于表示直方圖行寬

float hranges_arr[] = { 0, 255 }; //圖像方塊範圍數組

float *phranges_arr = hranges_arr; //cvCreateHist參數是一個二級指針,是以要用指針指向數組然後傳參

//建立直方圖

CvHistogram *hist[9];

for (int i = 0; i < 9; ++i){

hist[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//建立一個一維的直方圖,行寬為255,多元密集數組,方塊範圍為0-255,bin均化

}

//計算直方圖

for (int i = 0; i < 9; ++i){

cvCalcHist(&i1[i], hist[i], 0, 0);

}
           

11. 加載模闆圖

IplImage* abcd_img[9] = { NULL };

char img_name[256] = { 0 };

for (int i = 0; i < 9; ++i){

sprintf(img_name, "d:\\img\\%d.jpg", i);

abcd_img[i] = cvLoadImage(img_name);

}
           

12. 轉換成灰階圖

//轉換灰階圖

IplImage* abcd_img_hdt[9] = { NULL };

for (int i = 0; i < 9; ++i){

abcd_img_hdt[i] = cvCreateImage(cvSize(abcd_img[i]->width, abcd_img[i]->height), 8, 1);

cvCvtColor(abcd_img[i], abcd_img_hdt[i], CV_BGR2GRAY);//CV_BGR2GRAY

}
           

13. 計算模闆圖的直方圖

//建立直方圖

CvHistogram *hist_abcd[9];

for (int i = 0; i < 9; ++i){

hist_abcd[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//建立一個一維的直方圖,行寬為255,多元密集數組,方塊範圍為0-255,bin均化

}

//計算模闆圖的直方圖

for (int i = 0; i < 9; ++i){

cvCalcHist(&abcd_img_hdt[i], hist_abcd[i], 0, 0);

}
           

14. 申請結構體,友善資料化管理

//申請結構體

p_image *p[9] = { NULL };

for (int i = 0; i <= num; ++i){

p[i] = (p_image *)malloc(sizeof(p_image));

}
           

15. 字元關聯

//字元關聯

char abcd[9] = { 'H', 'E', 'L', 'L', 'O', 'W', 'O', 'R', 'D' };

for (int i = 0; i <= num; ++i){

p[i]->c_name = abcd[i];

}
           

16. 結構體與模闆直方圖管理,形成結構化

//直方圖關聯

for (int i = 0; i < num; ++i){

p[i]->img_zft = hist_abcd[i];

}
           

17. 比對圖像

//比對圖像

char j_c[9];//用于存儲比對到的字元

for (int i = 0; i < num; ++i){

for (int j = 0; j < num; ++j){

double Compare = cvCompareHist(hist[i], p[j]->img_zft, 0); //使用CV_COMP_CORREL方法進行對比

if (Compare == 1.){//100%正确 這裡我們精準度調整高一點,因為直方圖比對真的是太差勁了,相似度很高

			j_c[i] = p[j]->c_name;

}}}
           

18. 列印比對結果

printf("檢測到%d個字母,分别是", num);

for (int i = 0; i < num; ++i){

printf("%c", j_c[i]);

if (i != 8){

printf(",");

}

}

for (int i = 0; i < num; ++i){

p[i]->c_name = abcd[i];


}
           

19. 顯示圖像

//顯示圖像

cvNamedWindow("image", 0);

cvNamedWindow("image_gray", 0);

cvNamedWindow("img_value", 0);

char a[9];

for (int i = 0; i < num; ++i){

sprintf(a, "%d", i);

cvShowImage(a, imgNo[i]);

}

cvShowImage("image", image);

cvShowImage("image_gray", img_gray);

cvShowImage("img_value", img_value);

cvWaitKey(0);
           

運作結果:

使用Opencv進行輪廓檢測,字元提取,簡單的直方圖字元識别!

完整代碼:

typedef struct p_image{

char c_name;

CvHistogram *img_zft;

}p_image;

int main()

{

//打開要識别字元的圖像

IplImage *image = cvLoadImage("d:\\1.png");

if (image == NULL){

printf("錯誤:無法打開該圖像檔案!");

}

//轉換到灰階圖

IplImage *img_gray = cvCreateImage(cvGetSize(image), image->depth, 1);

cvCvtColor(image, img_gray, CV_BGR2GRAY);

//二值化

IplImage *img_value = cvCreateImage(cvGetSize(img_gray), image->depth, 1);

cvThreshold(img_gray, img_value, 100, 255, CV_THRESH_BINARY_INV);

//尋找輪廓

CvMemStorage *pStorage = cvCreateMemStorage(0);

CvSeq *pConInner = NULL;

int num = cvFindContours(img_value, pStorage, &pConInner, sizeof(CvContour),

CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

IplImage* imgNo[9] = { NULL };

IplImage *i1[9] = { NULL };

//字元提取

for (int i = 0; i < num; i++)

{


CvRect rc = cvBoundingRect(pConInner);

//cvDrawRect(image, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(255, 0, 0)); //為了防止裁剪時出現矩陣顔色,是以将這段代碼屏蔽掉!

imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);

cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//設定源圖像ROI

cvCopy(image, imgNo[i]); //裁剪

pConInner = pConInner->h_next;

}

//儲存與讀取

char name[256];

for (int i = 0; i < 9; ++i){

sprintf(name, "d:\\img\\%d.jpg", i);

cvSaveImage(name, imgNo[i]);

}

char b[256];

for (int i = 0; i < 9; ++i){

sprintf(b, "d:\\img\\%d.jpg", i);

imgNo[i] = cvLoadImage(b);

}

//灰階轉換

for (int i = 0; i < 9; ++i){

i1[i] = cvCreateImage(cvSize(imgNo[i]->width, imgNo[i]->height), 8, 1);

cvCvtColor(imgNo[i], i1[i], CV_BGR2GRAY);//CV_BGR2GRAY

}

//計算原圖的直方圖

int arr_size = 255; //定義一個變量用于表示直方圖行寬

float hranges_arr[] = { 0, 255 }; //圖像方塊範圍數組

float *phranges_arr = hranges_arr; //cvCreateHist參數是一個二級指針,是以要用指針指向數組然後傳參

//建立直方圖

CvHistogram *hist[9];

for (int i = 0; i < 9; ++i){

hist[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//建立一個一維的直方圖,行寬為255,多元密集數組,方塊範圍為0-255,bin均化

}

//計算直方圖

for (int i = 0; i < 9; ++i){

cvCalcHist(&i1[i], hist[i], 0, 0);

}

//字元與圖像關聯

//加載圖像

IplImage* abcd_img[9] = { NULL };

char img_name[256] = { 0 };

for (int i = 0; i < 9; ++i){

sprintf(img_name, "d:\\img\\%d.jpg", i);

abcd_img[i] = cvLoadImage(img_name);

}

//轉換灰階圖

IplImage* abcd_img_hdt[9] = { NULL };

for (int i = 0; i < 9; ++i){

abcd_img_hdt[i] = cvCreateImage(cvSize(abcd_img[i]->width, abcd_img[i]->height), 8, 1);

cvCvtColor(abcd_img[i], abcd_img_hdt[i], CV_BGR2GRAY);//CV_BGR2GRAY

}

//建立直方圖

CvHistogram *hist_abcd[9];

for (int i = 0; i < 9; ++i){

hist_abcd[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//建立一個一維的直方圖,行寬為255,多元密集數組,方塊範圍為0-255,bin均化

}

//計算模闆圖的直方圖

for (int i = 0; i < 9; ++i){

cvCalcHist(&abcd_img_hdt[i], hist_abcd[i], 0, 0);

}

//關聯圖像

p_image *p[9] = { NULL };

for (int i = 0; i <= num; ++i){

p[i] = (p_image *)malloc(sizeof(p_image));

}

//字元關聯

char abcd[9] = { 'H', 'E', 'L', 'L', 'O', 'W', 'O', 'R', 'D' };

for (int i = 0; i <= num; ++i){

p[i]->c_name = abcd[i];

}

//直方圖關聯

for (int i = 0; i < num; ++i){

p[i]->img_zft = hist_abcd[i];

}

//比對圖像

//用于結果

char j_c[9];

for (int i = 0; i < num; ++i){

for (int j = 0; j < num; ++j){

double Compare = cvCompareHist(hist[i], p[j]->img_zft, 0); //使用CV_COMP_CORREL方法進行對比

if (Compare == 1.){ //100%正确 這裡我們精準度調整高一點,因為直方圖比對真的是太差勁了,相似度很高

j_c[i] = p[j]->c_name;


}

}

}

printf("檢測到%d個字母,分别是", num);

for (int i = 0; i < num; ++i){

printf("%c", j_c[i]);

if (i != 8){

printf(",");

}

}

for (int i = 0; i < num; ++i){

p[i]->c_name = abcd[i];


}

//顯示圖像

cvNamedWindow("image", 0);

cvNamedWindow("image_gray", 0);

cvNamedWindow("img_value", 0);

char a[9];

for (int i = 0; i < num; ++i){

sprintf(a, "%d", i);

cvShowImage(a, imgNo[i]);

}

cvShowImage("image", image);

cvShowImage("image_gray", img_gray);

cvShowImage("img_value", img_value);

cvWaitKey(0);

return 0;

}

           
參考文獻:
           

https://blog.csdn.net/bjbz_cxy/article/details/79741725

繼續閱讀