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

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

这就要用到opencv的轮廓提取、旋转变换等接口知识了。

首先,看看要识别的图片,这里我随便找了一张单据的图片

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

这里要把图片中的单据提取出来,扶正,并且单据要充满整个图片。(其实还需要识别单据左上方的条形码,并获取其坐标,有点复杂,这里就不说了)

这里说说我的思路:首先,灰度化、二值化,根据亮度的不同,把单据部分的轮廓提取出来,然后填充单据部分,将其作为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));

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

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

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

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

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


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

你可能感兴趣的:(opencv)