OpenCV实现“全能扫描王”的图像矫正功能

这篇具有很好参考价值的文章主要介绍了OpenCV实现“全能扫描王”的图像矫正功能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:

相信很多人手机里都装了个“扫描全能王”APP,平时可以用它来可以扫描一些证件、文本,确实很好用,第一次用的时候确实感觉功能很强大啊算法很牛逼啊。但是仔细一想,其实这些实现起来也是很简单的,我想了下,实现的步骤应该就只有下面三个:

  1. 将证件轮廓找到

  1. 提取证件矩形轮廓四点进行透视变换

  1. 二值化

知道原理之后,我马上利用强大的opencv开发一个类似“全能扫描王”扫描工具。

整理一下我们要制作的这个扫描工具有哪些功能:

  1. 图像的信息区域的提取与矫正

  1. 图像的二值化

  1. 锐化和增强

第二第三点都非常简单,那么制作这个工具的难点完全落在了第一点“ 图像的信息区域的提取与矫正”上了。在编码实现的过程中,确实有很多坑需要踩一踩。

效果图:

我们先展示一下效果,我们有这么一个用手机拍摄的图片

OpenCV实现“全能扫描王”的图像矫正功能

经过扫描工具一番处理后变成这样子。也就是说,我们将原图中的那个文件抠了了出来,并且完成矫正。

OpenCV实现“全能扫描王”的图像矫正功能

实现过程查阅了大量资料,也看了网上很多类似的博客,前辈们实现过相类似的透视变换的代码,但是他们的代码实现的都不理想,很多图片根本没法检测。不过还是可以从前人的经验中获取到很多好想法的。

代码实现:

第一步,二值化+高斯滤波+膨胀+canny边缘提取

一开始我是没有采取形态学处理的,仅仅是二值化+高斯滤波+canny边缘提取的策略,但是实际运行下效果并不好,原因在于有一些图片的信息区域轮廓没法闭合,这就导致了信息区域轮廓没法提取。但是加入适当的膨胀后,效果就好多了。

Matsrc= imread("1.png");
    imshow("src img", src);
    Matsource= src.clone();

    Matbkup= src.clone();

    Matimg= src.clone();
    cvtColor(img, img, CV_RGB2GRAY);   //二值化
    imshow("gray", img);
    //equalizeHist(img, img);//imshow("equal", img);
    GaussianBlur(img, img, Size(5, 5), 0, 0);  //高斯滤波//获取自定义核Matelement= getStructuringElement(MORPH_RECT, Size(3, 3)); //第一个参数MORPH_RECT表示矩形的卷积核,当然还可以选择椭圆形的、交叉型的//膨胀操作
    dilate(img, img, element);  //实现过程中发现,适当的膨胀很重要
    imshow("dilate", img);
    Canny(img, img, 30, 120, 3);   //边缘提取
    imshow("get contour", img);
}

轮廓提取效果如下:

OpenCV实现“全能扫描王”的图像矫正功能

第二步,轮廓查找并筛选

一般情况下,我们提取到的轮廓不会像上图那样的干净,而是带有很多干扰项轮廓,如果我们不能很好的剔除这些轮廓,我们根本没法找出我们想要的信息区域。我筛选轮廓的方法很简单,就是找出一张图片中面积最大的那个轮廓作为我们的信息区域轮廓,这招真是屡试不爽,因为根据我们日常经验,我们对一张证件或者文件性扫描拍摄,证件区域占整张图片的面积肯定是最大的。

    vector<vector<Point> > contours;
    vector<vector<Point> > f_contours;
    std::vector<cv::Point> approx2;
    //注意第5个参数为CV_RETR_EXTERNAL,只检索外框  findContours(img, f_contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找轮廓//求出面积最大的轮廓int max_area = 0;
    int index;
    for (int i = 0; i < f_contours.size(); i++)
    {
        double tmparea = fabs(contourArea(f_contours[i]));
        if (tmparea > max_area)
        {
            index = i;
            max_area = tmparea;
        }

    }
    contours.push_back(f_contours[index]);

筛选出我们所需的轮廓

OpenCV实现“全能扫描王”的图像矫正功能

第三步,找出这个四边形轮廓的四个顶点

因为我们需要轮廓的四个顶点坐标来实现透视变换,现在的问题来了:我们怎么利用这个四边形轮廓的点集来找出初四边形的四个顶点?

这真是一个大难题!

一开始我的想法是这样子的:直接从四边形点集中筛选出四个定点(比如x坐标最大的那个坐标肯定是四边形右上角坐标或者右下角坐标,x坐标最小的那个坐标肯定是左上角或者下角的那个坐标,如此类推),但是这种想法实现起来是很有问题的而因为它很受限于四边形的姿态,所以一个思路一直没法进行下去。如果大家有仅依赖四边形点集就能找出四边形的四个顶点坐标的方法,请告诉我,我们一同探讨。

所以我切换了另外一个思路:基于直线交点的思路。我们首先使用霍夫变换找出四边形的边,然后求两两直线的交点不就是四边形的定点吗?的确是这样子的,但是实际操作起来也是问题多多啊。

最大的问题就是,我们怎么保证我们使用霍夫变换找到的直线刚好就是形成四边形的四条直线?

所以我们就必须不断地去改变霍夫变换的参数,不断迭代,来求出一个可以形成四边形的直线情况。

那什么情况的直线我们不能接受?

  1. 两两直线过于接近我们排除

  1. 两两直线没有交点我们排除

  1. 检测出来的直线数目不是4条我们排除

如果找到了满足条件的四条直线,我们就可以去计算他们的交点了。算法如下:

cv::Point2fcomputeIntersect(cv::Vec4i a, cv::Vec4i b)
{
    int x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3];
    int x3 = b[0], y3 = b[1], x4 = b[2], y4 = b[3];

    if (float d = ((float)(x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4)))
    {
        cv::Point2f pt;
        pt.x = ((x1*y2 - y1*x2) * (x3 - x4) - (x1 - x2) * (x3*y4 - y3*x4)) / d;
        pt.y = ((x1*y2 - y1*x2) * (y3 - y4) - (y1 - y2) * (x3*y4 - y3*x4)) / d;
        return pt;
    }
    elsereturn cv::Point2f(-1, -1);
}

计算出四个交点后,我们不能完全信任他们就是我们要找的四个顶点,所以继续筛选:

  1. 如果两两定点的距离过近,我们排除

bool IsGoodPoints = true;

//保证点与点的距离足够大以排除错误点for (int i = 0; i < corners.size(); i++)
{
for (int j = i + 1; j < corners.size(); j++)
{
    int distance = sqrt((corners[i].x - corners[j].x)*(corners[i].x - corners[j].x) + (corners[i].y - corners[j].y)*(corners[i].y - corners[j].y));
    if (distance < 5)
    {
        IsGoodPoints = false;
    }
}
}

if (!IsGoodPoints) continue;
  1. 如果这四个点构成不了四边形我们排除

  cv::approxPolyDP(cv::Mat(corners), approx, cv::arcLength(cv::Mat(corners), true) * 0.02, true);

if (lines.size() == 4 && corners.size() == 4 && approx.size() == 4)
{
    flag = 1;
    break;
}

如果都通过以上筛选条件的,我们就可以认为他们就是我们找的那四个顶点,这时我们就可以停止迭代,进行顶点排序,即确定这四个顶点哪个是左上角点,哪个又是右下点。

算法如下:

boolx_sort(const Point2f & m1, const Point2f & m2){
    return m1.x < m2.x;
}

//确定四个点的中心线voidsortCorners(std::vector<cv::Point2f>& corners,
    cv::Point2f center){
    std::vector<cv::Point2f> top, bot;
    vector<Point2f> backup = corners;

    sort(corners, x_sort);  //注意先按x的大小给4个点排序for (int i = 0; i < corners.size(); i++)
    {
        if (corners[i].y < center.y && top.size() < 2)    //这里的小于2是为了避免三个顶点都在top的情况
            top.push_back(corners[i]);
        else
            bot.push_back(corners[i]);
    }
    corners.clear();

    if (top.size() == 2 && bot.size() == 2) 
    {
        //cout << "log" << endl;
        cv::Point2f tl = top[0].x > top[1].x ? top[1] : top[0];
        cv::Point2f tr = top[0].x > top[1].x ? top[0] : top[1];
        cv::Point2f bl = bot[0].x > bot[1].x ? bot[1] : bot[0];
        cv::Point2f br = bot[0].x > bot[1].x ? bot[0] : bot[1];


        corners.push_back(tl);
        corners.push_back(tr);
        corners.push_back(br);
        corners.push_back(bl);
    }
    else
    {
        corners = backup;
    }
}

第四步,四点法透射变换

我们拿到原图信息区域四边形的四个顶点,现在我们还需要变换后图像的四个顶点才可以实现投射变换。

求变换后四个顶点坐标前我们还需要做的一件事就是,确定变换后的图像尺寸。第一种方法就是人工指定,比如我直接规定好变换后的图片大小是bbb*aaa。第二种方法就是,通过计算确定信息区域的尺寸,也就是说,信息区域有多大,我们变换后的图像就有多大。

既然我们知道了四边形的四个顶点了,那么我们可以直接求两点的距离来确定四边形的长宽。变换后的图像高度宽度可以这么确定:

int g_dst_hight;  //最终图像的高度int g_dst_width; //最终图像的宽度voidCalcDstSize(const vector<cv::Point2f>& corners){
    int h1 = sqrt((corners[0].x - corners[3].x)*(corners[0].x - corners[3].x) + (corners[0].y - corners[3].y)*(corners[0].y - corners[3].y));
    int h2 = sqrt((corners[1].x - corners[2].x)*(corners[1].x - corners[2].x) + (corners[1].y - corners[2].y)*(corners[1].y - corners[2].y));
    g_dst_hight = MAX(h1, h2);

    int w1 = sqrt((corners[0].x - corners[1].x)*(corners[0].x - corners[1].x) + (corners[0].y - corners[1].y)*(corners[0].y - corners[1].y));
    int w2 = sqrt((corners[2].x - corners[3].x)*(corners[2].x - corners[3].x) + (corners[2].y - corners[3].y)*(corners[2].y - corners[3].y));
    g_dst_width = MAX(w1, w2);
}

透射变换:

cv::Mat quad = cv::Mat::zeros(g_dst_hight, g_dst_width, CV_8UC3);
std::vector<cv::Point2f> quad_pts;
quad_pts.push_back(cv::Point2f(0, 0));
quad_pts.push_back(cv::Point2f(quad.cols, 0));
quad_pts.push_back(cv::Point2f(quad.cols, quad.rows));

quad_pts.push_back(cv::Point2f(0, quad.rows));

cv::Mat transmtx = cv::getPerspectiveTransform(corners, quad_pts);
cv::warpPerspective(source, quad, transmtx, quad.size());

所有关键步骤都已经说明完毕,运行一下代码,看看效果。

OpenCV实现“全能扫描王”的图像矫正功能

再拍一些其他图片,看看处理效果

OpenCV实现“全能扫描王”的图像矫正功能
OpenCV实现“全能扫描王”的图像矫正功能
OpenCV实现“全能扫描王”的图像矫正功能
OpenCV实现“全能扫描王”的图像矫正功能

试一下带有干扰背景的图像,效果还是不错的

OpenCV实现“全能扫描王”的图像矫正功能

额外效果:二值化

有些时候还需要将一些文本或者证件弄成扫描模样,那我们就加入二值化实现该效果。

Mat local,gray;
cvtColor(quad, gray, CV_RGB2GRAY);
int blockSize = 25;
int constValue = 10;
adaptiveThreshold(gray, local, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSize, constValue);

imshow("二值化", local);

二值化效果挺好的

OpenCV实现“全能扫描王”的图像矫正功能
OpenCV实现“全能扫描王”的图像矫正功能

源码下载:

OpenCV实现“全能扫描王”的图像矫正功能文章来源地址https://www.toymoban.com/news/detail-481475.html

到了这里,关于OpenCV实现“全能扫描王”的图像矫正功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 图像处理的未来:揭秘扫描全能王的AI驱动创新

    🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 目录 前言 一、底层技术-智能扫描引擎AI-Scan 图像视觉矫正 去干扰技术 去阴影 去手指 去摩

    2024年02月12日
    浏览(37)
  • OpenCV图像矫正技术基础

    OpenCV图像矫正技术是一种基于计算机视觉技术的图像处理技术,能够将一张图像进行矫正,使得图像看起来更加规则、清晰。 OpenCV图像矫正技术的实现思路: 1、获取图像:首先需要获取要处理的图像,对图像进行预处理,将图像转换成一种可用的格式,例如OpenCV中的Mat格式

    2024年02月13日
    浏览(40)
  • OpenCV图像矫正

    1. 针对边缘比较明显的图片,使用基于轮廓提取的矫正方法。 基本步骤: 1)变为灰度图; 2)Canny边缘检测: Canny算法的基本思想是寻找一张图片中灰度强度变化最强(梯度方向)的位置; 3)使用 OpenCV 的 findcontours() 提取轮廓(一个轮廓对应一组点集); 4)根据轮廓求最

    2024年02月04日
    浏览(40)
  • OpenCV利用透视变换矫正图像

    案例:使用OpenCV将一张折射的图片给矫正过来 实现步骤: 1.载入图像 2.图像灰度化 3.二值分割 4.形态学操作去除噪点 5.轮廓发现 6.使用霍夫直线检测,检测上下左右四条直线(有可能是多条,但是无所谓) 7.绘制出直线 8.寻找与定位上下左右是条直线 9.拟合四条直线方程 1

    2024年02月06日
    浏览(65)
  • opencv图像畸变矫正:源码学习

    参考资料:相机标定(4) 矫正畸变 undistort()和initUndistortRectifyMap() 背景: opencv提供了直接进行畸变矫正的代码,因在项目中需要使用畸变矫正,因此研究一下opencv中畸变矫正的相关接口与代码,便于学习提升与二次开发。 opencv在文档中对相机标定与畸变矫正的原理做了简单

    2024年02月10日
    浏览(47)
  • opencv对相机进行畸变矫正,及从矫正后的图像坐标反求原来的对应坐标

    目前有个项目,需要用到热成像相机。但是这个热成像相机它的畸变比较厉害,因此需要用标定板进行标定,从而消除镜头畸变。 同时需要实现用户用鼠标点击校正后的画面后,显示用户点击位置的像素所代表的温度。 另外热成像sdk中还有个功能:选定一个rect,可以返回这

    2024年02月16日
    浏览(54)
  • 扫描全能王引领AI时代,助您轻松实现文档数字化!

    目录 一、前言 1、什么是OCR 2、如何利用 Python 实现文字识别 二、扫描全能王介绍 1、什么是智能高清滤镜 2、滤镜主要技术点 3、在日常生活的表现 4、什么是AI-Scan 5、AI-Scan的特点 5.1、图像视觉矫正 5.2、反光消除技术 5.3、去干扰技术 6、处理对比 三、总结 1、体验总结 2、对

    2024年02月11日
    浏览(44)
  • 《数字图像处理-OpenCV/Python》连载(1)前言

    本书京东优惠购书链接:https://item.jd.com/14098452.html 写作背景 编写本书的初衷,源自作者学习数字图像处理的经历。 在创新实验班开设的专业创新教育课程中,我选择的是数字图像处理方向。老师向我推荐的教材是冈萨雷斯的《数字图像处理》。学习的开始阶段非常困难。教

    2024年02月11日
    浏览(64)
  • 使用python-opencv对双目摄像头进行立体视觉图像矫正,去畸变

            1、一张棋盘图         可以直接从opencv官方github下载,这是一个拥有10*7个格子的棋盘,共有 9*6个角点,每个格子24mm ,本文所使用的就是这一个棋盘。你需要将它打印在A4纸上用于后续使用。(也可以根据官方教程自行设置棋盘大小OpenCV: Create calibration pattern)

    2024年02月10日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包