使用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; /* 指向第一個快序列 */
相關理論知識:在圖像處理中閾值是什么意思?
二. 開始編寫代碼
編寫代碼前需要準備一張實驗圖像!
可自行保存到本地,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);
運行結果:
二值化圖像是很利用我們做輪廓檢測的,因為二值化的圖像中不會有其他摻雜顏色在里面影響輪廓檢測準確!
完整代碼:
//打開要識別字符的圖像
?? ?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;
?? ?}
最后在將其顯示出來
char a[9];
?? ?for (int i = 0; i < 9; ++i){
?? ??? ?sprintf(a, "%d", i);
?? ??? ?cvShowImage(a, imgNo[i]);
?? ?}
運行結果:
完整代碼:
//打開要識別字符的圖像
?? ?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繪制灰度直方圖/對比
注意前提,你要有模板圖像,這里我把樣本圖像分享給各位!
由于模板圖像較多,我上傳到csdn上,供需要的人下載正規字符模板,下載需要兩積分,如果不想下載可以使用下列圖像,然后復用上面的代碼裁剪出字符然后使用cvSeveImage函數保存到本地即可!
然后使用下列代碼:
//打開要識別字符的圖像
?? ?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模板匹配函數要求尺寸一致,這里為了方便就不編寫尺寸變換的代碼了,只是做一個示例,所以我就直接用
這張圖的字符樣本做模板了,
拿出來分享給各位:
????
有需要可自行保存到本地,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);
運行結果:
完整代碼:
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?
總結
以上是生活随笔為你收集整理的使用Opencv进行轮廓检测,字符提取,简单的直方图字符识别!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: FFMPEG中H.264的算法文档--整
- 下一篇: [Tesseract]Tesseract