目錄
一.使用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; /* 指向第一個快序列 */
相關理論知識:在圖像進行中門檻值是什麼意思?
二. 開始編寫代碼
編寫代碼前需要準備一張實驗圖像!
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyN4UzM1MTN0ETOyMDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
可自行儲存到本地,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;
}
//為了防止裁剪時出現矩陣顔色,是以将這段代碼屏蔽掉! 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模闆比對函數要求尺寸一緻,這裡為了友善就不編寫尺寸變換的代碼了,隻是做一個示例,是以我就直接用
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyN4UzM1MTN0ETOyMDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
這張圖的字元樣本做模闆了,
拿出來分享給各位:
有需要可自行儲存到本地,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