您的位置:首页 > 运维架构

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

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

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

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

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







完整的工程,可以到这里下载:工程下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: