首页 > 学院 > 开发设计 > 正文

使用opencv提取单据轮廓并旋转后生成图片

2019-11-08 01:47:56
字体:
来源:转载
供稿:网友

最近做图像识别方面的工作,需要对图片中的票据进行提取、识别,票据可能并不是正着放进去的,所以还需要进行旋转,还涉及到一些坐标转换的问题。

这就要用到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));

这两个接口主要是用来旋转变换的,上方的是获取旋转矩阵,下方是旋转变换

可以根据下方的图片看到程序处理的过程

完整的工程,可以到这里下载:工程下载


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表