标签(空格分隔): 计算机图形学 数字图像处理
背景
支付宝和微信对数字货币的推广起到了积极的作用,大多数现代人都不怎么用手点钞了,取而代之的是数字货币。随着数字货币的发展,终有一天纸币将会退出历史舞台。那一天一定会有很多人想将那个年代的钞票一张张拍下来留念。
将一张百元大钞拍下来,手抖拍得又歪又斜,怎么办呢?那么多张钞票要微调处理,不可能每次都使用PS,那样工作量会很太。利用图像矫正技术可以解决这个问题,首先要弄明白几个技术原理点。
图像矫正技术知识储备
- 目标识别(匹配、最佳统计分类器、神经网络)
- 轮廓检测与提取(矩形区域裁剪、灰度化、二值化、ROI)
- 霍夫变换(Hough),在本次实验中并没有使用上,因为钞票图像有明显的边缘轮廓,而Hough主要用在没有明显边缘轮廓的图像当中,例如文本处理。
目标识别
轮廓检测指在包含目标和背景的数字图像中,忽略背景和目标内部的纹理以及噪声干扰的影响,采用一定的技术和方法来实现目标轮廓提取的过程。它是目标检测、形状分析、目标识别和目标跟踪等技术的重要基础。来自百科的解释。
若钞票的背景图像比较复杂,在检测过程中肯定会受到噪声干扰的。这一步将会利用深度学习强大的学习能力解决这个目标轮廓区域检测的难题,这样效率会更高。这里涉及到一个重要点,通过设定指定的阀值就能判断目标区域的倾斜识别,让程序变得更智能,无需人工介入来判断。由于本文篇幅有限,感兴趣的同学自行找相关资料参考学习。
轮廓检测与提取
目标识别系统检测出钞票之后会在目标区域周围标注边界框,就可以获取边界框的坐标信息 ,然后根据L坐标信息的范围取相反方向,在相反方向区域内对图像背景填充黑色再进行图像灰度化、二值化、检测钞票的轮廓、寻找钞票轮廓边界矩阵、获得角度a、a不断旋转矫正,最后将已水平的图像区域抠取出来另存为一张新图像。
实现过程
1、目标识别
目标识别系统检测到钞票得到边界框的坐标信息是
问题的重点是网络模型怎样检测出钞票四个角的边缘特征,而不是只检测到红色的外边框。如果这一步解决了,后面问题将迎刃而解,此问题先暂时缓一下。
假设网络模型可以给出以下坐标参数
2、感兴趣区域ROI,将不规则区域填充黑色
Mat mask = Mat::zeros(src.size(),CV_8UC1);
Mat dst;
vector<vector<Point2i>> contours;
vector<Point2i> points;
points.push_back(Point2i(x1, y1));
points.push_back(Point2i(x2, y2));
points.push_back(Point2i(x3, y3));
points.push_back(Point2i(x4, y4));
contours.push_back(points);
drawContours(mask, contours, 0, Scalar::all(255), -1);
src.copyTo(dst, mask);
imwrite("images/result_process.jpg", dst);
将钞票的背景图像区域填充黑色
3、轮廓检测与提取
Mat gray, binImg;
//灰度化
cvtColor(dst,gray, COLOR_RGB2GRAY);
imshow("灰度图", gray);
//二值化
threshold(gray, binImg, 100, 200, CV_THRESH_BINARY);
imshow("二值化", binImg);
contours.clear();
vector<Rect> boundRect(contours.size());
//注意第5个参数为CV_RETR_EXTERNAL,只检索外框
findContours(binImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找轮廓
cout << contours.size() << endl;
for (int i = 0; i < contours.size(); i++)
{
//需要获取的坐标
CvPoint2D32f rectpoint[4];
CvBox2D rect =minAreaRect(Mat(contours[i]));
cvBoxPoints(rect, rectpoint); //获取4个顶点坐标
//与水平线的角度
float angle = rect.angle;
cout << angle << endl;
int line1 = sqrt(pow(rectpoint[1].y - rectpoint[0].y,2)+pow(rectpoint[1].x - rectpoint[0].x,2));
int line2 = sqrt(pow(rectpoint[3].y - rectpoint[0].y,2)+pow(rectpoint[3].x - rectpoint[0].x,2));
//面积太小的直接过滤
if (line1 * line2 < 600)
{
continue;
}
//为了让正方形横着放,所以旋转角度是不一样的。竖放的,给他加90度,翻过来
if (line1 > line2)
{
angle = 90 + angle;
}
//新建一个感兴趣的区域图,大小跟原图一样大
Mat RoiSrcImg(dst.rows, dst.cols, CV_8UC3); //注意这里必须选CV_8UC3
RoiSrcImg.setTo(0); //颜色设置为黑色
//对得到的轮廓填充一下
drawContours(binImg, contours, -1, Scalar(255),CV_FILLED);
//抠图到RoiSrcImg
dst.copyTo(RoiSrcImg, binImg);
//再显示一下看看,除了感兴趣的区域,其他部分都是黑色的了
namedWindow("RoiSrcImg", 1);
imshow("RoiSrcImg", RoiSrcImg);
//创建一个旋转后的图像
Mat RatationedImg(RoiSrcImg.rows, RoiSrcImg.cols, CV_8UC1);
RatationedImg.setTo(0);
//对RoiSrcImg进行旋转
Point2f center = rect.center; //中心点
Mat M2 = getRotationMatrix2D(center, angle, 1);//计算旋转加缩放的变换矩阵
warpAffine(RoiSrcImg, RatationedImg, M2, RoiSrcImg.size(),1, 0, Scalar(0));//仿射变换
imshow("旋转之后", RatationedImg);
imwrite("images/test_temp.jpg", RatationedImg); //将矫正后的图片保存下来
}
角度调整之后的图片如下
对旋转后的图片进行ROI轮廓区域的提取
#if 1
vector<vector<Point> > contours2;
Mat raw = imread("images/test_temp.jpg");
Mat SecondFindImg;
cvtColor(raw, SecondFindImg, COLOR_BGR2GRAY); //灰度化
threshold(SecondFindImg, SecondFindImg, 80, 200, CV_THRESH_BINARY);
findContours(SecondFindImg, contours2, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
for (int j = 0; j < contours2.size(); j++)
{
//这时候其实就是一个长方形了,所以获取rect。
Rect rect = boundingRect(Mat(contours2[j]));
//面积太小的轮廓直接过滤,通过设置过滤面积大小,可以保证只拿到外框。
if (rect.area() < 600)
{
continue;
}
Mat dstImg = raw(rect);
imshow("dst", dstImg);
imwrite("images/test_result.jpg", dstImg);
}
#endif
}
最终结果如下,这个误差应该可以在接受的范围之内。
总结
算法名称基于轮廓提取的矫正算法,本次实验的算法是在Madcola的基础上优化大部分代码,并结合深度学习目标识别检测钞票图像的角点特征预测。由于时间紧迫,本文的算法和实验并不完美,还有很多值得改进的地方。