使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!
一.使用Opencv進(jìn)行輪廓檢測!
所需函數(shù):
1.?cvFindContours
函數(shù)功能:從二值圖像中檢索輪廓,并返回檢測到的輪廓的個(gè)數(shù)
函數(shù)原型:
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));
參數(shù)介紹:
CvArr* image:要檢測輪廓的圖像,必須為二值圖
CvMemStorage* storage:存儲(chǔ)輪廓的容器
?CvSeq** first_contour:輸出參數(shù),用于存儲(chǔ)指向第一個(gè)外接輪廓。是一個(gè)鏈表,h_next用于指向下一個(gè)外接輪廓
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:檢索所有的輪廓,并重構(gòu)嵌套輪廓的整個(gè)層次。
int method
邊緣近似方法(除了CV_RETR_RUNS使用內(nèi)置的近似,其他模式均使用此設(shè)定的近似算法)。可取值如下:
CV_CHAIN_CODE:以Freeman鏈碼的方式輸出輪廓,所有其他方法輸出多邊形(頂點(diǎn)的序列)。
CV_CHAIN_APPROX_NONE:將所有的連碼點(diǎn),轉(zhuǎn)換成點(diǎn)。
CV_CHAIN_APPROX_SIMPLE:壓縮水平的、垂直的和斜的部分,也就是,函數(shù)只保留他們的終點(diǎn)部分。
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
偏移量,用于移動(dòng)所有輪廓點(diǎn)。當(dāng)輪廓是從圖像的ROI提取的,并且需要在整個(gè)圖像中分析時(shí),這個(gè)參數(shù)將很有用。
返回值:檢測到的輪廓數(shù)量
2.cvThreshold
函數(shù)功能:對(duì)單通道數(shù)組應(yīng)用固定閾值操作。該函數(shù)的典型應(yīng)用是對(duì)灰度圖像進(jìn)行閾值操作得到二值圖像。
函數(shù)原型:
void cvThreshold
( const CvArr* src,
CvArr* dst,
double threshold,
double max_value,
int threshold_type );
參數(shù)介紹:
src:原始數(shù)組 (單通道 , 8-bit of 32-bit 浮點(diǎn)數(shù))。
dst:輸出數(shù)組,必須與 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
函數(shù)功能:用來創(chuàng)建一個(gè)內(nèi)存存儲(chǔ)器,來統(tǒng)一管理各種動(dòng)態(tài)對(duì)象的內(nèi)存。
函數(shù)原型:
CvMemStorage* ?cvCreateMemStorage( int block_size = 0);
參數(shù)介紹:
int block_size = 0:對(duì)應(yīng)內(nèi)存器中每個(gè)內(nèi)存塊的大小,為0時(shí)內(nèi)存塊默認(rèn)大小為64k。
返回值:返回一個(gè)新創(chuàng)建的內(nèi)存存儲(chǔ)器指針。
4.?cvBoundingRect
函數(shù)功能:計(jì)算點(diǎn)集的最外面(up-right)矩形邊界。
函數(shù)原型:
CvRect ?cvBoundingRect( CvArr* points, int update = 0);
參數(shù)介紹:
CvArr* points:二級(jí)點(diǎn)矩陣
int update:
更新標(biāo)識(shí)。
下面是輪廓類型和標(biāo)識(shí)的一些可能組合:
update=0, contour ~ CvContour*: 不計(jì)算矩形邊界,但直接由輪廓頭的 rect 域得到。
update=1, contour ~ CvContour*: 計(jì)算矩形邊界,而且將結(jié)果寫入到輪廓頭的 rect 域中 header。
update=0, contour ~ CvSeq* or CvMat*: 計(jì)算并返回邊界矩形。
update=1, contour ~ CvSeq* or CvMat*: 產(chǎn)生運(yùn)行錯(cuò)誤 (runtime error is raised)。
函數(shù) cvBoundingRect 返回二維點(diǎn)集的最外面 (up-right)矩形邊界。 [1]?
所需結(jié)構(gòu)體:
1. CvMemStorage
結(jié)構(gòu)體介紹:
typedef struct CvMemStorage
?
{
?
? ? int signature;
?
? ? CvMemBlock* bottom; ? ? ? ? ? ? ? ?/**< 第一分配塊。 ? ? ? ? ? ? ? ? ? */
?
? ? CvMemBlock* top; ? ? ? ? ? ? ? ? ? ? /**< 當(dāng)前內(nèi)存塊——堆棧的頂部。 */
?
? ? struct ?CvMemStorage* parent; /**< 根據(jù)需要從父塊獲取新的塊。 */
?
? ? int block_size; ? ? ? ? ? ? ? ? ? ? ? ? ?/**< 塊的大小。 ? ? ? ? ? ? ? ? ? ? ? ? ? */
?
? ? int free_space; ? ? ? ? ? ? ? ? ? ? ? ? /**< 當(dāng)前塊中的剩余空閑空間。 ? */
?
}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; /* 前一個(gè)序列 */?
? ? struct CvSeq* h_next; /* 后一個(gè)序列 */?
? ? struct CvSeq* v_prev; /* 第二級(jí)前一個(gè)序列 */?
? ? struct CvSeq* v_next; /* 第二級(jí)后一個(gè)序列 */?
? ? int total; /* 元素的總個(gè)數(shù) */?
? ? int elem_size;/* 元素的尺寸 */?
? ? char* block_max;/* 上一塊的最大塊 */?
? ? char* ptr; /* 當(dāng)前寫指針 */?
? ? int delta_elems; /*序列中快的大小
? ? ? ? ? ? ? ? ? ? ? ? (序列粒度) */?
? ? CvMemStorage* storage; /*序列的存儲(chǔ)位置 */?
? ? CvSeqBlock* free_blocks; /* 未分配的塊序列 */?
? ? CvSeqBlock* first; /* 指向第一個(gè)快序列 */
相關(guān)理論知識(shí):在圖像處理中閾值是什么意思?
二. 開始編寫代碼
編寫代碼前需要準(zhǔn)備一張實(shí)驗(yàn)圖像!
可自行保存到本地,png格式!
1. 加載測試圖像
//打開要識(shí)別字符的圖像
?? ?IplImage *image = cvLoadImage("d:\\1.png");
?? ?if (image == NULL){
?? ??? ?printf("錯(cuò)誤:無法打開該圖像文件!");
?? ?}
2.圖像二值化
//轉(zhuǎn)換到灰度圖
?? ?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. 獲取輪廓在圖像中的矩陣坐標(biāo)
//根據(jù)輪廓坐標(biāo)使用方框標(biāo)出來
?? ?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);
運(yùn)行結(jié)果:
二值化圖像是很利用我們做輪廓檢測的,因?yàn)槎祷膱D像中不會(huì)有其他摻雜顏色在里面影響輪廓檢測準(zhǔn)確!
完整代碼:
//打開要識(shí)別字符的圖像
?? ?IplImage *image = cvLoadImage("d:\\1.png");
?? ?if (image == NULL){
?? ??? ?printf("錯(cuò)誤:無法打開該圖像文件!");
?? ?}
//轉(zhuǎn)換到灰度圖
?? ?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);
//根據(jù)輪廓坐標(biāo)使用方框標(biāo)出來
?? ?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);
三. 字符提取
已經(jīng)將字符輪廓用矩形給標(biāo)出來了,那么接下來字符提取就較為簡單了!
我們可以復(fù)用上面的代碼,在繪制輪廓前加入:
IplImage* imgNo[9] = { NULL };
用于存儲(chǔ)分割出來的圖像
在使用cvCopy函數(shù)對(duì)其進(jìn)行裁剪即可!
//根據(jù)輪廓坐標(biāo)使用方框標(biāo)出來
?? ?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));?? ?//為了防止裁剪時(shí)出現(xiàn)矩陣顏色,所以將這段代碼屏蔽掉!
?? ??? ?imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
?? ??? ?cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//設(shè)置源圖像ROI
?? ??? ?cvCopy(image, imgNo[i]);?? ?//裁剪,將裁剪的字符圖像放入到imgNo數(shù)組中去
?? ??? ?pConInner = pConInner->h_next;
?? ?}
最后在將其顯示出來
char a[9];
?? ?for (int i = 0; i < 9; ++i){
?? ??? ?sprintf(a, "%d", i);
?? ??? ?cvShowImage(a, imgNo[i]);
?? ?}
運(yùn)行結(jié)果:
完整代碼:
//打開要識(shí)別字符的圖像
?? ?IplImage *image = cvLoadImage("d:\\1.png");
?? ?if (image == NULL){
?? ??? ?printf("錯(cuò)誤:無法打開該圖像文件!");
?? ?}
?? ?//轉(zhuǎn)換到灰度圖
?? ?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 };
?? ?//根據(jù)輪廓坐標(biāo)使用方框標(biāo)出來
?? ?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));?? ?//為了防止裁剪時(shí)出現(xiàn)矩陣顏色,所以將這段代碼屏蔽掉!
?? ??? ?imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
?? ??? ?cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//設(shè)置源圖像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);
四. 文字識(shí)別
在文字識(shí)別,你可以使用其他方法,本博客中使用最簡單的直方圖匹配,后續(xù)的車牌識(shí)別中,我會(huì)寫一篇通過Opencv訓(xùn)練分練器,通過樣本文件來識(shí)別提高識(shí)別率!
相關(guān)鏈接: 使用Opencv繪制灰度直方圖/對(duì)比
注意前提,你要有模板圖像,這里我把樣本圖像分享給各位!
由于模板圖像較多,我上傳到csdn上,供需要的人下載正規(guī)字符模板,下載需要兩積分,如果不想下載可以使用下列圖像,然后復(fù)用上面的代碼裁剪出字符然后使用cvSeveImage函數(shù)保存到本地即可!
然后使用下列代碼:
//打開要識(shí)別字符的圖像
?? ?IplImage *image = cvLoadImage("d:\\1.png");
?? ?if (image == NULL){
?? ??? ?printf("錯(cuò)誤:無法打開該圖像文件!");
?? ?}
?? ?//轉(zhuǎn)換到灰度圖
?? ?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 };
?? ?//根據(jù)輪廓坐標(biāo)使用方框標(biāo)出來
?? ?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));?? ?//為了防止裁剪時(shí)出現(xiàn)矩陣顏色,所以將這段代碼屏蔽掉!
?? ??? ?imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
?? ??? ?cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//設(shè)置源圖像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);
運(yùn)行之后你的D盤下就會(huì)出現(xiàn)一個(gè)img文件里面包含了所有的正規(guī)字符模板圖!
開始編寫識(shí)別代碼
這里我們不復(fù)用上面的代碼,重新編寫一份,因?yàn)槟0迤ヅ渌惴ㄓ行┑胤接袉栴},所以這里重寫一遍給大家注明一下!
注意上面的模型建議用在其他識(shí)別算法上,不建議用在模板圖片上,matchTemplate模板匹配函數(shù)要求尺寸一致,這里為了方便就不編寫尺寸變換的代碼了,只是做一個(gè)示例,所以我就直接用
這張圖的字符樣本做模板了,
拿出來分享給各位:
????
有需要可自行保存到本地,jpg格式!
下面開始編寫代碼:
1. 定義一個(gè)結(jié)構(gòu)體用于關(guān)聯(lián)圖像與字符
typedef struct p_image{
?? ?char c_name;
?? ?CvHistogram *img_zft;
}p_image;
2. 打開測試圖像
//打開要識(shí)別字符的圖像
?? ?IplImage *image = cvLoadImage("d:\\1.png");
?? ?if (image == NULL){
?? ??? ?printf("錯(cuò)誤:無法打開該圖像文件!");
?? ?}
3. 圖像二值化
//轉(zhuǎn)換到灰度圖
?? ?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. 申請(qǐng)變量用于字符提取
IplImage* imgNo[9] = { NULL };
6. 申請(qǐng)變量用于轉(zhuǎn)化字符提取的直方圖
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));?? ?//為了防止裁剪時(shí)出現(xiàn)矩陣顏色,所以將這段代碼屏蔽掉!
?? ??? ?imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
?? ??? ?cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//設(shè)置源圖像ROI
?? ??? ?cvCopy(image, imgNo[i]);?? ?//裁剪
?? ??? ?pConInner = pConInner->h_next;
?? ?}
8. 重新保存與讀取
為什么要加這段重復(fù)代碼?
原因:經(jīng)過我測試,Opencv里面的數(shù)據(jù)是沒有經(jīng)過壓縮的,所以當(dāng)你打開模板圖時(shí)模板圖格式為jpg,經(jīng)過jpeg算法壓縮,當(dāng)你使用直方圖比對(duì)時(shí)比對(duì)會(huì)有差異,注意加這段代碼僅針對(duì)直方圖的情況來做修改的!
其他匹配方法無需增加這段代碼!
吐槽一下:直方圖匹配真的是太爛了,直方圖只能用于計(jì)算!
//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. 灰度圖轉(zhuǎn)換
//灰度轉(zhuǎn)換
?? ?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. 計(jì)算原圖的直方圖
//計(jì)算原圖的直方圖
?? ?int arr_size = 255; ? ? ? ? ? ? ? ? //定義一個(gè)變量用于表示直方圖行寬 ?
?? ?float hranges_arr[] = { 0, 255 }; ? ? ? //圖像方塊范圍數(shù)組 ?
?? ?float *phranges_arr = hranges_arr; ? ? ?//cvCreateHist參數(shù)是一個(gè)二級(jí)指針,所以要用指針指向數(shù)組然后傳參 ?
?? ?//創(chuàng)建直方圖 ?
?? ?CvHistogram *hist[9];
?? ?for (int i = 0; i < 9; ++i){
?? ??? ?hist[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//創(chuàng)建一個(gè)一維的直方圖,行寬為255,多維密集數(shù)組,方塊范圍為0-255,bin均化 ?
?? ?}
?? ?//計(jì)算直方圖
?? ?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. 轉(zhuǎn)換成灰度圖
?? ?//轉(zhuǎn)換灰度圖
?? ?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. 計(jì)算模板圖的直方圖
//創(chuàng)建直方圖 ?
?? ?CvHistogram *hist_abcd[9];
?? ?for (int i = 0; i < 9; ++i){
?? ??? ?hist_abcd[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//創(chuàng)建一個(gè)一維的直方圖,行寬為255,多維密集數(shù)組,方塊范圍為0-255,bin均化 ?
?? ?}
?? ?//計(jì)算模板圖的直方圖
?? ?for (int i = 0; i < 9; ++i){
?? ??? ?cvCalcHist(&abcd_img_hdt[i], hist_abcd[i], 0, 0);
?? ?}
14. 申請(qǐng)結(jié)構(gòu)體,方便數(shù)據(jù)化管理
//申請(qǐng)結(jié)構(gòu)體
?? ?p_image *p[9] = { NULL };
?? ?for (int i = 0; i <= num; ++i){
?? ??? ?p[i] = (p_image *)malloc(sizeof(p_image));
?? ?}
15. 字符關(guān)聯(lián)
//字符關(guān)聯(lián)
?? ?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. 結(jié)構(gòu)體與模板直方圖管理,形成結(jié)構(gòu)化
//直方圖關(guān)聯(lián)
?? ?for (int i = 0; i < num; ++i){
?? ??? ?p[i]->img_zft = hist_abcd[i];
?? ?}
17. 匹配圖像
//匹配圖像
?? ?char j_c[9];//用于存儲(chǔ)匹配到的字符
?? ?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方法進(jìn)行對(duì)比
?? ??? ??? ?if (Compare == 1.){//100%正確 這里我們精準(zhǔn)度調(diào)整高一點(diǎn),因?yàn)橹狈綀D比對(duì)真的是太差勁了,相似度很高
?? ??? ??? ?j_c[i] = p[j]->c_name;
}}}
18. 打印匹配結(jié)果
printf("檢測到%d個(gè)字母,分別是", 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);
運(yùn)行結(jié)果:
完整代碼:
typedef struct p_image{
?? ?char c_name;
?? ?CvHistogram *img_zft;
}p_image;
int main()
{
?? ?//打開要識(shí)別字符的圖像 ?
?? ?IplImage *image = cvLoadImage("d:\\1.png");
?? ?if (image == NULL){
?? ??? ?printf("錯(cuò)誤:無法打開該圖像文件!");
?? ?}
?? ?//轉(zhuǎn)換到灰度圖 ?
?? ?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)); ? ?//為了防止裁剪時(shí)出現(xiàn)矩陣顏色,所以將這段代碼屏蔽掉! ?
?? ??? ?imgNo[i] = cvCreateImage(cvSize(rc.width, rc.height), IPL_DEPTH_8U, 3);
?? ??? ?cvSetImageROI(image, cvRect(rc.x, rc.y, rc.width, rc.height));//設(shè)置源圖像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);
?? ?}
?? ?//灰度轉(zhuǎn)換 ?
?? ?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 ? ?
?? ?}
?? ?//計(jì)算原圖的直方圖 ?
?? ?int arr_size = 255; ? ? ? ? ? ? ? ? //定義一個(gè)變量用于表示直方圖行寬 ? ?
?? ?float hranges_arr[] = { 0, 255 }; ? ? ? //圖像方塊范圍數(shù)組 ? ?
?? ?float *phranges_arr = hranges_arr; ? ? ?//cvCreateHist參數(shù)是一個(gè)二級(jí)指針,所以要用指針指向數(shù)組然后傳參 ? ?
?? ?//創(chuàng)建直方圖 ? ?
?? ?CvHistogram *hist[9];
?? ?for (int i = 0; i < 9; ++i){
?? ??? ?hist[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//創(chuàng)建一個(gè)一維的直方圖,行寬為255,多維密集數(shù)組,方塊范圍為0-255,bin均化 ? ?
?? ?}
?? ?//計(jì)算直方圖 ?
?? ?for (int i = 0; i < 9; ++i){
?? ??? ?cvCalcHist(&i1[i], hist[i], 0, 0);
?? ?}
?? ?//字符與圖像關(guān)聯(lián) ?
?? ?//加載圖像 ?
?? ?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);
?? ?}
?? ?//轉(zhuǎn)換灰度圖 ?
?? ?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 ? ?
?? ?}
?? ?//創(chuàng)建直方圖 ? ?
?? ?CvHistogram *hist_abcd[9];
?? ?for (int i = 0; i < 9; ++i){
?? ??? ?hist_abcd[i] = cvCreateHist(1, &arr_size, CV_HIST_ARRAY, &phranges_arr, 1);//創(chuàng)建一個(gè)一維的直方圖,行寬為255,多維密集數(shù)組,方塊范圍為0-255,bin均化 ? ?
?? ?}
?? ?//計(jì)算模板圖的直方圖 ?
?? ?for (int i = 0; i < 9; ++i){
?? ??? ?cvCalcHist(&abcd_img_hdt[i], hist_abcd[i], 0, 0);
?? ?}
?? ?//關(guān)聯(lián)圖像 ?
?? ?p_image *p[9] = { NULL };
?? ?for (int i = 0; i <= num; ++i){
?? ??? ?p[i] = (p_image *)malloc(sizeof(p_image));
?? ?}
?? ?//字符關(guān)聯(lián) ?
?? ?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];
?? ?}
?? ?//直方圖關(guān)聯(lián) ?
?? ?for (int i = 0; i < num; ++i){
?? ??? ?p[i]->img_zft = hist_abcd[i];
?? ?}
?? ?//匹配圖像 ?
?? ?//用于結(jié)果 ?
?? ?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方法進(jìn)行對(duì)比 ?
?? ??? ??? ?if (Compare == 1.){ //100%正確 這里我們精準(zhǔn)度調(diào)整高一點(diǎn),因?yàn)橹狈綀D比對(duì)真的是太差勁了,相似度很高 ?
?? ??? ??? ??? ?j_c[i] = p[j]->c_name;
?
?? ??? ??? ?}
?? ??? ?}
?? ?}
?? ?printf("檢測到%d個(gè)字母,分別是", 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?
總結(jié)
以上是生活随笔為你收集整理的使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: FFMPEG中H.264的算法文档--整
- 下一篇: [Tesseract]Tesseract