最近做图像识别方面的工作,需要对图片中的票据进行提取、识别,票据可能并不是正着放进去的,所以还需要进行旋转,还涉及到一些坐标转换的问题。
这就要用到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));
这两个接口主要是用来旋转变换的,上方的是获取旋转矩阵,下方是旋转变换
可以根据下方的图片看到程序处理的过程
完整的工程,可以到这里下载:工程下载
新闻热点
疑难解答