最近做圖像識別方面的工作,需要對圖片中的票據進行提取、識別,票據可能并不是正著放進去的,所以還需要進行旋轉,還涉及到一些坐標轉換的問題。
這就要用到opencv的輪廓提取、旋轉變換等接口知識了。
首先,看看要識別的圖片,這里我隨便找了一張單據的圖片

這里要把圖片中的單據提取出來,扶正,并且單據要充滿整個圖片。(其實還需要識別單據左上方的條形碼,并獲取其坐標,有點復雜,這里就不說了)
這里說說我的思路:首先,灰度化、二值化,根據亮度的不同,把單據部分的輪廓提取出來,然后填充單據部分,將其作為mask把單據部分拿出來,然后旋轉扶正,再次識別,去除邊框外的部分,剩下部分生成圖片保存。這個過程其實就是這么簡單,下面直接上代碼:
void GetContoursPic(const char* pSrcFileName, const char* pDstFileName){ iplImage* pSrcImg = NULL; IplImage* pFirstFindImg = NULL; IplImage* PRoiSrcImg = NULL; IplImage* pRatationedImg = NULL; IplImage* pSecondFindImg = NULL; IplImage* pDstImg = NULL; CvSeq* pFirstSeq = NULL; CvSeq* pSecondSeq = NULL; CvMemStorage* storage = cvCreateMemStorage(0); pSrcImg = cvLoadImage(pSrcFileName, 1); pFirstFindImg = cvCreateImage(cvGetSize(pSrcImg), IPL_DEPTH_8U, 1); //檢索外圍輪廓 cvCvtColor(pSrcImg, pFirstFindImg, CV_BGR2GRAY); //灰度化 cvThreshold(pFirstFindImg, pFirstFindImg, 100, 200, CV_THRESH_BINARY); //設置閾值,二值化 //注意第5個參數為CV_RETR_EXTERNAL,只檢索外框 int nCount = cvFindContours(pFirstFindImg, storage, &pFirstSeq, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //顯示看一下 cvNamedWindow ("pSrcImg", 1); cvShowImage("pSrcImg", pSrcImg); for (;pFirstSeq != NULL; pFirstSeq = pFirstSeq->h_next) { if (pFirstSeq->total < 600) //太小的不考慮,這個要考慮圖片分辨率大小 { continue; } //需要獲取的坐標 CvPoint2D32f rectpoint[4]; CvBox2D End_Rage2D = cvMinAreaRect2(pFirstSeq); //尋找包圍矩形,獲取角度 cvBoxPoints(End_Rage2D, rectpoint); //獲取4個頂點坐標 //與水平線的角度 float angle = End_Rage2D.angle; CString strFort = _T(""); strFort.Format(_T("/n angle:%f /n"), angle); OutputDebugString(strFort); //如果角度超過5度,就需要做旋轉,否則不需要 if (angle > 5 || angle < -5) { //計算兩條邊的長度 int line1 = sqrt((rectpoint[1].y-rectpoint[0].y)*(rectpoint[1].y-rectpoint[0].y)+(rectpoint[1].x-rectpoint[0].x)*(rectpoint[1].x-rectpoint[0].x)); int line2 = sqrt((rectpoint[3].y-rectpoint[0].y)*(rectpoint[3].y-rectpoint[0].y)+(rectpoint[3].x-rectpoint[0].x)*(rectpoint[3].x-rectpoint[0].x)); //為了讓正方形橫著放,所以旋轉角度是不一樣的 if (line1 > line2) // { angle = 90 + angle; } //新建一個感興趣的區域圖,大小跟原圖一樣大 pRoiSrcImg = cvCreateImage(cvGetSize(pSrcImg), pSrcImg->depth, pSrcImg->nChannels); cvSet(pRoiSrcImg, CV_RGB(0,0,0)); //顏色都設置為黑色 //對得到的輪廓填充一下 cvDrawContours(pFirstFindImg, pFirstSeq, CV_RGB(255, 255, 255), CV_RGB(255, 255, 255), -1, CV_FILLED, 8); //把pFirstFindImg這個填充的區域從pSrcImg中摳出來放到pRoiSrcImg上 cvCopy(pSrcImg, pRoiSrcImg, pFirstFindImg); //再顯示一下看看,除了感興趣的區域,其他部分都是黑色的了 cvNamedWindow ("pRoiSrcImg", 1); cvShowImage("pRoiSrcImg", pRoiSrcImg); //創建一個旋轉后的圖像 pRatationedImg = cvCreateImage(cvGetSize(pRoiSrcImg), pRoiSrcImg->depth, pRoiSrcImg->nChannels); //對pRoiSrcImg進行旋轉 CvPoint2D32f center = End_Rage2D.center; //中心點 double map[6]; CvMat map_matrix = cvMat(2, 3, CV_64FC1, map); cv2DRotationMatrix(center, angle, 1.0, &map_matrix); cvWarpAffine(pRoiSrcImg, pRatationedImg, &map_matrix, CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS, cvScalarAll(0)); //顯示一下旋轉后的圖像 cvNamedWindow ("pRatationedImg", 1); cvShowImage ("pRatationedImg", pRatationedImg); //對旋轉后的圖片進行輪廓提取 pSecondFindImg = cvCreateImage(cvGetSize(pRatationedImg), IPL_DEPTH_8U, 1); cvCvtColor(pRatationedImg, pSecondFindImg, CV_BGR2GRAY); //灰度化 cvThreshold(pSecondFindImg, pSecondFindImg, 80, 200, CV_THRESH_BINARY); nCount = cvFindContours(pSecondFindImg, storage, &pSecondSeq, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); for (;pSecondSeq != NULL; pSecondSeq = pSecondSeq->h_next) { if (pSecondSeq->total < 600) //太小的不考慮 { continue; } //這時候其實就是一個長方形了,所以獲取rect CvRect rect = cvBoundingRect(pSecondSeq); cvSetImageROI(pRatationedImg, rect); CvSize dstSize; dstSize.width = rect.width; dstSize.height = rect.height; pDstImg = cvCreateImage(dstSize, pRatationedImg->depth, pRatationedImg->nChannels); cvCopy(pRatationedImg, pDstImg, 0); cvResetImageROI(pRatationedImg); //保存成圖片 cvSaveImage(pDstFileName, pDstImg); } } else { //角度比較小,本來就是正放著的,所以獲取矩形 CvRect rect = cvBoundingRect(pFirstSeq); //把這個矩形區域設置為感興趣的區域 cvSetImageROI(pSrcImg, rect); CvSize dstSize; dstSize.width = rect.width; dstSize.height = rect.height; pDstImg = cvCreateImage(dstSize, pSrcImg->depth, pSrcImg->nChannels); //拷貝過來 cvCopy(pSrcImg, pDstImg, 0); cvResetImageROI(pSrcImg); //保存 cvSaveImage(pDstFileName, pDstImg); } } //顯示一下最后的結果 cvNamedWindow ("Contour", 1); cvShowImage("Contour", pDstImg); cvWaitKey(0); //釋放所有 cvReleaseMemStorage(&storage); if (pRoiSrcImg) { cvReleaseImage(&pRoiSrcImg); } if (pRatationedImg) { cvReleaseImage(&pRatationedImg); } if (pSecondFindImg) { cvReleaseImage(&pSecondFindImg); } cvReleaseImage(&pDstImg); cvReleaseImage(&pFirstFindImg); cvReleaseImage(&pSrcImg); }調用的時候直接使用類似這樣既可:GetContoursPic("D://clip//IMG_1431.JPG", "D://clip//IMG_1431_ratation.JPG");
這個過程中,其實最重要的部分就是設置提取的閾值、提取和旋轉,首先看設置閾值
cvThreshold(pFirstFindImg, pFirstFindImg, 100, 200, CV_THRESH_BINARY); //設置閾值,二值化
這里第三、第四和第五個參數很重要,在第五個參數為CV_THRESH_BINARY的時候,只要圖片中的亮度超過100的,就顯示200的亮度以便于提取,這樣檢索外框的接口就可以提取了
int nCount = cvFindContours(pFirstFindImg, storage, &pFirstSeq, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
這里第三和第五個參數很重要,CV_RETR_EXTERNAL表示只檢索外框,pFirstSeq是檢索后獲取到的外框鏈表,可從中篩選出需要的外框。
cv2DRotationMatrix(center, angle, 1.0, &map_matrix);cvWarpAffine(pRoiSrcImg, pRatationedImg, &map_matrix, CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS, cvScalarAll(0));
這兩個接口主要是用來旋轉變換的,上方的是獲取旋轉矩陣,下方是旋轉變換
可以根據下方的圖片看到程序處理的過程



完整的工程,可以到這里下載:工程下載
新聞熱點
疑難解答