实战 | 图像矫正技术

2019 年 2 月 28 日 计算机视觉life

 点“计算机视觉life”关注,置顶更快接收消息!


本文由Madcola授权转载,未经允许禁止二次转载

原文地址:

https://www.cnblogs.com/skyfsm/p/6902524.html

什么是图像的矫正?

举个例子就好明白了。

我的好朋友小明给我拍了这几张照片,因为他的拍照技术不咋地,照片都拍得歪歪扭扭的,比如下面这些照片:

人民币

发票

文本

这些图片让人看得真不舒服!看个图片还要歪脖子看,实在是太烦人了!我叫小明帮我扫描一下一本教科书,小明把每一页书都拍成上面的文本那样了。好气啊那该怎么办呢?一页一页用PS来处理?1000页的矫正啊,当然交给计算机去做!

真的,对于图像矫正的问题,在图像处理领域还真得多,比如人民币的矫正、文本的矫正、车牌的矫正、身份证矫正等等。这些都是因为拍摄者总不可能100%正确地拍摄好图片,这就要求我们通过后期的图像处理技术将图片还原好,才能进一步做后面的处理,比如数字分割啊数字识别啊,不然歪歪扭扭的文字数字,想识别出来估计就很难了。

上面几个图,我们在日常生活中遇到的可不少,因为拍摄时拍的不好,导致拍出来的图片歪歪扭扭的,很不自然,那么我们能不能把这些图片尽可能地矫正过来呢?

基于轮廓提取的矫正算法

OpenCV告诉我们,没问题!工具我给你,算法你自己设计!

比如图一,我要想将人民币矫正,并且把人民币整个抠出来保存,该怎么做?那就涉及到了图像的矫正和感兴趣区域提取两大技术了。

总的来说,要进行进行图像矫正,至少有以下几项知识储备:

  • 轮廓提取技术

  • 霍夫变换知识

  • ROI感兴趣区域知识

下面以人民币矫正、发票矫正、文本矫正为例,一步步剖析如何实现图像矫正。

首先分析如何矫正人民币。

比如我们要矫正这张人民币,思路应该是怎么样?

首先分析这张图的特点。

在这张图里,人民币有一定的倾斜角度,但是角度不大;人民币的背景是黑色的,而且人民币的边缘应该比较明显。

没错,我们就抓住人民币的的边缘比较明显来做文章!我们是不是可以先把人民币的轮廓找出来(找出来的轮廓当然就是一个大大的矩形),然后用矩形去包围它,得到他的旋转角度,然后根据得到的角度进行旋转,那样不就可以实现矫正了吗!

再详细地总结处理步骤:

  1. 图片灰度化

  2. 阈值二值化

  3. 检测轮廓

  4. 寻找轮廓的包围矩阵,并且获取角度

  5. 根据角度进行旋转矫正

  6. 对旋转后的图像进行轮廓提取

  7. 对轮廓内的图像区域抠出来,成为一张独立图像

我把该矫正算法命名为基于轮廓提取的矫正算法,因为其关键技术就是通过轮廓来获取旋转角度。

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;

//第一个参数:输入图片名称;第二个参数:输出图片名称
void GetContoursPic(const char* pSrcFileName, const char* pDstFileName)
{
    Mat srcImg = imread(pSrcFileName);
    imshow("原始图", srcImg);
    Mat gray, binImg;
    //灰度化
    cvtColor(srcImg, gray, COLOR_RGB2GRAY);
    imshow("灰度图", gray);
    //二值化
    threshold(gray, binImg, 100200, CV_THRESH_BINARY);
    imshow("二值化", binImg);

    vector<vector<Point> > contours;
    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((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));
        //rectangle(binImg, rectpoint[0], rectpoint[3], Scalar(255), 2);
        //面积太小的直接pass
        if (line1 * line2 < 600)
        {
            continue;
        }

        //为了让正方形横着放,所以旋转角度是不一样的。竖放的,给他加90度,翻过来  
        if (line1 > line2) 
        {
            angle = 90 + angle;
        }

        //新建一个感兴趣的区域图,大小跟原图一样大  
        Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3)//注意这里必须选CV_8UC3
        RoiSrcImg.setTo(0); //颜色都设置为黑色  
        //imshow("新建的ROI", RoiSrcImg);
        //对得到的轮廓填充一下  
        drawContours(binImg, contours, -1, Scalar(255),CV_FILLED);

        //抠图到RoiSrcImg
        srcImg.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(),10, Scalar(0));//仿射变换 
        imshow("旋转之后", RatationedImg);
        imwrite("r.jpg", RatationedImg); //将矫正后的图片保存下来
    }

#if 1
    //对ROI区域进行抠图

    //对旋转后的图片进行轮廓提取  
    vector<vector<Point> > contours2;
    Mat raw = imread("r.jpg");
    Mat SecondFindImg;
    //SecondFindImg.setTo(0);
    cvtColor(raw, SecondFindImg, COLOR_BGR2GRAY);  //灰度化  
    threshold(SecondFindImg, SecondFindImg, 80200, CV_THRESH_BINARY);
    findContours(SecondFindImg, contours2, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
    //cout << "sec contour:" << contours2.size() << endl;

    for (int j = 0; j < contours2.size(); j++)
    {
        //这时候其实就是一个长方形了,所以获取rect  
        Rect rect = boundingRect(Mat(contours2[j]));
        //面积太小的轮廓直接pass,通过设置过滤面积大小,可以保证只拿到外框
        if (rect.area() < 600)
        {
            continue;
        }
        Mat dstImg = raw(rect);
        imshow("dst", dstImg);
        imwrite(pDstFileName, dstImg);
    }
#endif


}


void main()
{
    GetContoursPic("6.jpg""FinalImage.jpg");
    waitKey();
}

(左右滑动试试)

效果依次如下:
原始图

二值化图

掩膜mask是这样的

旋转矫正之后

将人民币区域抠出来

该算法的效果还是很不错的!那赶紧试试其他图片,我把倾斜的发票图像拿去试试。

原始图

倾斜矫正之后

最后把目标区域抠出来,成为单独的照片。

基于直线探测的矫正算法

上面的算法可以很好的处理人民币和发票两种情况的倾斜矫正,那文本矫正可以吗?我赶紧试了一下,结果是失败的。

原图

算法矫正后,还是原样,矫正失败。

认真分析一下,还是很容易看出文本矫正失败的原因的。

原因就在于,人民币图像和发票图像他们有明显的的边界轮廓,而文本图像没有。文本图像的背景是白色的,所以我们没有办法像人民币发票那类有明显边界的矩形物体那样,提取出轮廓并旋转矫正。

经过深入分析可以看出,虽然文本类图像没有明显的边缘轮廓,但是他们有一个很重要的特征,那就是每一行文字都是呈现一条直线形状,而且这些直线都是平行的!

对于这种情况,我想到了另一种方法:基于直线探测的矫正算法

首先介绍一下我的算法思路:

  1. 用霍夫线变换探测出图像中的所有直线

  2. 计算出每条直线的倾斜角,求他们的平均值

  3. 根据倾斜角旋转矫正

  4. 最后根据文本尺寸裁剪图片

然后给出OpenCV的实现算法:

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;

#define ERROR 1234

//度数转换
double DegreeTrans(double theta)
{
    double res = theta / CV_PI * 180;
    return res;
}


//逆时针旋转图像degree角度(原尺寸)    
void rotateImage(Mat src, Mat& img_rotate, double degree)
{
    //旋转中心为图像中心    
    Point2f center;
    center.x = float(src.cols / 2.0);
    center.y = float(src.rows / 2.0);
    int length = 0;
    length = sqrt(src.cols*src.cols + src.rows*src.rows);
    //计算二维旋转的仿射变换矩阵  
    Mat M = getRotationMatrix2D(center, degree, 1);
    warpAffine(src, img_rotate, M, Size(length, length), 10, Scalar(255,255,255));//仿射变换,背景色填充为白色  
}

//通过霍夫变换计算角度
double CalcDegree(const Mat &srcImage, Mat &dst)
{
    Mat midImage, dstImage;

    Canny(srcImage, midImage, 502003);
    cvtColor(midImage, dstImage, CV_GRAY2BGR);

    //通过霍夫变换检测直线
    vector<Vec2f> lines;
    HoughLines(midImage, lines, 1, CV_PI / 18030000);//第5个参数就是阈值,阈值越大,检测精度越高
    //cout << lines.size() << endl;

    //由于图像不同,阈值不好设定,因为阈值设定过高导致无法检测直线,阈值过低直线太多,速度很慢
    //所以根据阈值由大到小设置了三个阈值,如果经过大量试验后,可以固定一个适合的阈值。

    if (!lines.size())
    {
        HoughLines(midImage, lines, 1, CV_PI / 18020000);
    }
    //cout << lines.size() << endl;

    if (!lines.size())
    {
        HoughLines(midImage, lines, 1, CV_PI / 18015000);
    }
    //cout << lines.size() << endl;
    if (!lines.size())
    {
        cout << "没有检测到直线!" << endl;
        return ERROR;
    }

    float sum = 0;
    //依次画出每条线段
    for (size_t i = 0; i < lines.size(); i++)
    {
        float rho = lines[i][0];
        float theta = lines[i][1];
        Point pt1, pt2;
        //cout << theta << endl;
        double a = cos(theta), b = sin(theta);
        double x0 = a*rho, y0 = b*rho;
        pt1.x = cvRound(x0 + 1000 * (-b));
        pt1.y = cvRound(y0 + 1000 * (a));
        pt2.x = cvRound(x0 - 1000 * (-b));
        pt2.y = cvRound(y0 - 1000 * (a));
        //只选角度最小的作为旋转角度
        sum += theta;

        line(dstImage, pt1, pt2, Scalar(55100195), 1, LINE_AA); //Scalar函数用于调节线段颜色

        imshow("直线探测效果图", dstImage);
    }
    float average = sum / lines.size(); //对所有角度求平均,这样做旋转效果会更好

    cout << "average theta:" << average << endl;

    double angle = DegreeTrans(average) - 90;

    rotateImage(dstImage, dst, angle);
    //imshow("直线探测效果图2", dstImage);
    return angle;
}


void ImageRecify(const char* pInFileName, const char* pOutFileName)
{
    double degree;
    Mat src = imread(pInFileName);
    imshow("原始图", src);
    Mat dst;
    //倾斜角度矫正
    degree = CalcDegree(src,dst);
    if (degree == ERROR)
    {
        cout << "矫正失败!" << endl;
        return;
    }
    rotateImage(src, dst, degree);
    cout << "angle:" << degree << endl;
    imshow("旋转调整后", dst);

    Mat resulyImage = dst(Rect(00, dst.cols, 500)); //根据先验知识,估计好文本的长宽,再裁剪下来
    imshow("裁剪之后", resulyImage);
    imwrite("recified.jpg", resulyImage); 
}


int main()
{
    ImageRecify("correct2.jpg""FinalImage.jpg");
    waitKey();
    return 0;
}

(左右滑动试试)

看看效果。这是原始图

直线探测的效果。

矫正之后的效果。

我们发现矫正之后的图像有较多留白,影响观看,所以需要进一步裁剪,保留文字区域。

赶紧再试多一张。

原始图

直线探测

矫正效果

进一步裁剪


可以看出,基于直线探测的矫正算法在文本处理上效果真的很不错!


最后总结一下两个算法的应用场景:

  • 基于轮廓提取的矫正算法更适用于车牌、身份证、人民币、书本、发票一类矩形形状而且边界明显的物体矫正。

  • 基于直线探测的矫正算法更适用于文本类的矫正。

推荐阅读

计算机视觉方向简介 | 从全景图恢复三维结构

计算机视觉方向简介 | 阵列相机立体全景拼接

计算机视觉方向简介 | 单目微运动生成深度图

计算机视觉方向简介 | 深度相机室内实时稠密三维重建

计算机视觉方向简介 | 深度图补全

计算机视觉方向简介 | 人体骨骼关键点检测综述

计算机视觉方向简介 | 人脸识别中的活体检测算法综述

计算机视觉方向简介 | 目标检测最新进展总结与展望

计算机视觉方向简介 | 唇语识别技术

计算机视觉方向简介 | 三维深度学习中的目标分类与语义分割

计算机视觉方向简介 | 用深度学习进行表格提取


欢迎关注公众号:计算机视觉life,一起探索计算机视觉新世界~

觉得有用,给个好看啦~  

登录查看更多
5

相关内容

一个跨平台的计算机视觉处理库,全称是Open Source Computer Vision。
最新《自然场景中文本检测与识别》综述论文,26页pdf
专知会员服务
69+阅读 · 2020年6月10日
【经典书】Python数据数据分析第二版,541页pdf
专知会员服务
193+阅读 · 2020年3月12日
【GitHub实战】Pytorch实现的小样本逼真的视频到视频转换
专知会员服务
35+阅读 · 2019年12月15日
【机器学习课程】Google机器学习速成课程
专知会员服务
164+阅读 · 2019年12月2日
 图像内容自动描述技术综述
专知会员服务
86+阅读 · 2019年11月17日
深度学习算法与架构回顾
专知会员服务
81+阅读 · 2019年10月20日
[综述]深度学习下的场景文本检测与识别
专知会员服务
77+阅读 · 2019年10月10日
最全综述 | 图像目标检测
计算机视觉life
31+阅读 · 2019年6月24日
实战 | 用Python做图像处理(二)
七月在线实验室
17+阅读 · 2018年5月25日
实战|手把手教你实现图象边缘检测!
全球人工智能
10+阅读 · 2018年1月19日
OCR技术浅析
机器学习研究会
40+阅读 · 2017年12月8日
OpenCV计算机视觉产品实战
炼数成金订阅号
12+阅读 · 2017年9月22日
基础|人脸识别的十个关键技术组成及原理!
全球人工智能
5+阅读 · 2017年7月27日
干货|全景视频拼接的关键技术分析
全球人工智能
13+阅读 · 2017年7月15日
简单车牌检测
计算机视觉战队
6+阅读 · 2017年6月23日
Heterogeneous Graph Transformer
Arxiv
27+阅读 · 2020年3月3日
Neural Image Captioning
Arxiv
5+阅读 · 2019年7月2日
Foreground-aware Image Inpainting
Arxiv
4+阅读 · 2019年1月17日
Arxiv
4+阅读 · 2018年3月14日
Arxiv
3+阅读 · 2018年2月12日
Arxiv
6+阅读 · 2018年1月14日
VIP会员
相关VIP内容
最新《自然场景中文本检测与识别》综述论文,26页pdf
专知会员服务
69+阅读 · 2020年6月10日
【经典书】Python数据数据分析第二版,541页pdf
专知会员服务
193+阅读 · 2020年3月12日
【GitHub实战】Pytorch实现的小样本逼真的视频到视频转换
专知会员服务
35+阅读 · 2019年12月15日
【机器学习课程】Google机器学习速成课程
专知会员服务
164+阅读 · 2019年12月2日
 图像内容自动描述技术综述
专知会员服务
86+阅读 · 2019年11月17日
深度学习算法与架构回顾
专知会员服务
81+阅读 · 2019年10月20日
[综述]深度学习下的场景文本检测与识别
专知会员服务
77+阅读 · 2019年10月10日
相关资讯
最全综述 | 图像目标检测
计算机视觉life
31+阅读 · 2019年6月24日
实战 | 用Python做图像处理(二)
七月在线实验室
17+阅读 · 2018年5月25日
实战|手把手教你实现图象边缘检测!
全球人工智能
10+阅读 · 2018年1月19日
OCR技术浅析
机器学习研究会
40+阅读 · 2017年12月8日
OpenCV计算机视觉产品实战
炼数成金订阅号
12+阅读 · 2017年9月22日
基础|人脸识别的十个关键技术组成及原理!
全球人工智能
5+阅读 · 2017年7月27日
干货|全景视频拼接的关键技术分析
全球人工智能
13+阅读 · 2017年7月15日
简单车牌检测
计算机视觉战队
6+阅读 · 2017年6月23日
相关论文
Heterogeneous Graph Transformer
Arxiv
27+阅读 · 2020年3月3日
Neural Image Captioning
Arxiv
5+阅读 · 2019年7月2日
Foreground-aware Image Inpainting
Arxiv
4+阅读 · 2019年1月17日
Arxiv
4+阅读 · 2018年3月14日
Arxiv
3+阅读 · 2018年2月12日
Arxiv
6+阅读 · 2018年1月14日
Top
微信扫码咨询专知VIP会员