基于轮廓提取的矫正算法

背景

支付宝和微信对数字货币的推广起到了积极的作用,大多数现代人都不怎么用手点钞了,取而代之的是数字货币。随着数字货币的发展,终有一天纸币将会退出历史舞台。那一天一定会有很多人想将那个年代的钞票一张张拍下来留念。
将一张百元大钞拍下来,手抖拍得又歪又斜,怎么办呢?那么多张钞票要微调处理,不可能每次都使用PS,那样工作量会很太。利用图像矫正技术可以解决这个问题,首先要弄明白几个技术原理点。

图像矫正技术知识储备

  1. 目标识别(匹配、最佳统计分类器、神经网络)
  2. 轮廓检测与提取(矩形区域裁剪、灰度化、二值化、ROI)
  3. 霍夫变换(Hough),在本次实验中并没有使用上,因为钞票图像有明显的边缘轮廓,而Hough主要用在没有明显边缘轮廓的图像当中,例如文本处理。

目标检测

轮廓检测指在包含目标和背景的数字图像中,忽略背景和目标内部的纹理以及噪声干扰的影响,采用一定的技术和方法来实现目标轮廓提取的过程。它是目标检测、形状分析、目标识别和目标跟踪等技术的重要基础。来自百科的解释。
若钞票的背景图像比较复杂,在检测过程中肯定会受到噪声干扰的。这一步将会利用深度学习强大的学习能力解决这个目标轮廓区域检测的难题,这样效率会更高。这里涉及到一个重要点,通过设定指定的阀值就能判断目标区域的倾斜识别,让程序变得更智能,无需人工介入来判断。由于本文篇幅有限,感兴趣的同学自行找相关资料参考学习。

轮廓检测与提取

目标识别系统检测出钞票之后会在目标区域周围标注边界框,就可以获取边界框的坐标信息 L ( p 1 , p 2 , p 3 , p 4 ) , p n = p ( x , y ) , n ∈ 1..4 L(p_1,p_2,p_3,p_4),p_n = p(x,y),n∈1..4 L(p1,p2,p3,p4)pn=p(x,y)n1..4 ,然后根据L坐标信息的范围取相反方向,在相反方向区域内对图像背景填充黑色再进行图像灰度化、二值化、检测钞票的轮廓、寻找钞票轮廓边界矩阵、获得角度a、a不断旋转矫正,最后将已水平的图像区域抠取出来另存为一张新图像。

#实现过程

1、目标检测

目标检测系统检测到钞票得到边界框的坐标信息是 L ( p 1 , p 2 , p 3 , p 4 ) L(p_1,p_2,p_3,p_4) L(p1,p2,p3,p4)

在这里插入图片描述
在这里插入图片描述

问题的重点是网络模型怎样检测出钞票四个角的边缘特征,而不是只检测到红色的外边框。如果这一步解决了,后面问题将迎刃而解,此问题先暂时缓一下。
假设网络模型可以给出以下坐标参数
x 1 = 1011 x_1 = 1011 x1=1011
y 1 = 750 y_1 = 750 y1=750
x 2 = 2031 x_2 = 2031 x2=2031
y 2 = 3300 y_2 = 3300 y2=3300
x 3 = 2031 x_3 = 2031 x3=2031
y 3 = 3300 y_3 = 3300 y3=3300
x 4 = 2856 x_4 = 2856 x4=2856
y 4 = 2352 y_4 = 2352 y4=2352

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的基础上优化大部分代码,并结合深度学习目标识别检测钞票图像的角点特征预测。由于时间紧迫,本文的算法和实验并不完美,还有很多值得改进的地方。

车牌识别算法计算机视觉领域中的一个热门话题,可以应用于交通管理、智能停车、安防监控等领域。下面我简单介绍一下基于 OpenCV 的车牌号识别算法。 1. 图像预处理 首先,需要对车牌图像进行预处理,包括灰度化、高斯滤波、二值化、膨胀和腐蚀等操作。这些操作可以使车牌区域更加突出,减少噪声干扰。 2. 车牌定位 车牌定位是车牌识别的关键步骤,其目的是将车牌从图像中分离出来。常用的车牌定位方法有基于颜色的方法和基于形状的方法。其中基于颜色的方法是通过颜色分割来定位车牌,而基于形状的方法是通过边缘检测和轮廓分析来定位车牌。 3. 字符分割 在将车牌分割成单个字符之前,需要对车牌进行旋转矫正,使其水平。然后,可以使用垂直投影法来确定字符之间的分割位置,然后将字符分割出来。 4. 字符识别 对于每个字符,可以使用字符特征提取和模式识别技术来进行识别。其中常用的特征提取方法有灰度共生矩阵、傅里叶变换和小波变换等。而模式识别技术包括支持向量机、神经网络和决策树等。 5. 车牌号码识别 对于识别出的单个字符,可以通过组合成车牌号码来完成车牌号码识别。常用的方法是基于模板匹配和基于特征的方法。 总的来说,基于 OpenCV 的车牌号识别算法需要经过图像预处理、车牌定位、字符分割、字符识别和车牌号码识别等多个步骤。其中每个步骤都有多个实现方法和技术选择,具体选用哪些方法需要根据实际情况进行选择。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值