场景

  • 扫描仪扫描到法律文件有白边,需要切掉,看上去比较真实
  • 人工操作成本高时间久
  • 希望程序自动实现,高效、准确

解决思路

  • 方法一:通过边缘检测+轮廓发现或者直线检测最大外矩形框实现
  • 方法二:通过二值分割+形态学方法+Hough直线找到最大外接矩形

相关API

函数原型

void cv::warpAffine     (   
        InputArray      src,
        OutputArray     dst,
        InputArray      M,
        Size    dsize,
        int     flags = INTER_LINEAR,
        int     borderMode = BORDER_CONSTANT,
        const Scalar &      borderValue = Scalar() 
    )

参数介绍

  • src: 输入图像
  • dst: 输出图像,尺寸由dsize指定,图像类型与原图像一致
  • M: 2X3的变换矩阵
  • dsize: 指定图像输出尺寸
  • flags: 插值算法标识符,有默认值INTER_LINEAR,如果插值算法为WARP_INVERSE_MAP
INTER_NEAAREST最临近插值算法
INTER_LINEAR线性插值算法
INTER_CUBIC双立方插值算法
INTER_AREA区域插值算法(使用像素区域关系的重采样时图像抽取的首选方法,但是当图像被放大,它类似与INTER_NEAREST方法)
INTER_LANCZOS4Lanczos插值算法(超过8×8)邻域的插值算法
INTER_MAX用于插值的掩模板
WARP_FILL_OUTLIERS标志位,用于填充目标图像的像素值,如果其中一些值对应于原图像中的异常值,那么这些值将被设置为0
WARP_INVERSE_MAP标志位,反变换
  • borderMode: 边界像素模式,有默认值BORDER_CONSTANT
  • borderValue: 边界取值,有默认值Scalar()即0

函数api

Mat cv::getRotationMatrix2D     (   Point2f     center,
        double      angle,
        double      scale 
    )   

参数介绍

  • center: Point2f类型,表示原图像的旋转中心
  • angle: double类型,表示图像旋转角度,角度为正则表示逆时针旋转,角度为负表示逆时针旋转(坐标原点是图像左上角)
  • scale: 缩放系数

代码演示

#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <iostream>
#include <math.h>

using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;

#define PIC_PATH "/work/opencv_pic/"
#define PIC_NAME "case1.jpg"
int cur_val = 190;
int max_val = 255;

Mat src,gray_src,dst;
void find_roi(int,void*);
int main (void)
{
    //获取完整的图片路径及名称
    string pic  =  string(PIC_PATH)+string(PIC_NAME);


    //打印图片路径
    cout << "pic  path is :"<<pic<<endl;

    //读取图片
    src  = imread(pic);

    //判断图片是否存在
    if(src.empty())
    {
        cout<<"pic is not exist!!!!"<<endl;
        return -1;
    }

    namedWindow("src pic",WINDOW_AUTOSIZE);
    imshow("src pic",src);


    createTrackbar("阈值调整","src pic",&cur_val,max_val,find_roi);
    find_roi(0,0);
    waitKey(0);
    destroyAllWindows();
    return 0;
}
void find_roi(int,void*)
{
    cvtColor(src,gray_src,COLOR_BGR2GRAY);
    Mat canny_output;
    Canny(gray_src,canny_output,cur_val,cur_val*2,3);

    namedWindow("canny pic",WINDOW_AUTOSIZE);
    imshow("canny pic",canny_output);


    Mat recimg;
    vector<vector<Point>> contours;   //定义轮廓集合
    vector<Vec4i> hireachy;    //定义轮廓之间的索引
    //查找轮廓
    findContours(canny_output,contours,hireachy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point(0,0));

    //查找轮廓限制最基本范围  目测最大轮廓不应小于以下值
    int minW = src.cols*0.2;
    int minH = src.rows*0.55;
    dst = Mat::zeros(src.size(),CV_8UC3);
    float angle;   //轮廓外包矩形的旋转角度
    RotatedRect minrect;  //轮廓外包的最小矩形
    for(size_t i=0;i<contours.size();i++)
    {
        minrect = minAreaRect(contours[i]);  //获取轮廓外包矩形
        angle = minrect.angle;   //获取矩形角度
        if(minrect.size.width>minW && minrect.size.height > minH)  //判断矩形是不是那个比较大的
        {
            Point2f pts[4];    //矩形四个角点
            minrect.points(pts);   //获取角点
            Mat contour_rect = Mat::zeros(src.size(),CV_8UC3);

            //绘制外接最大矩形
            for(size_t i=0;i<4;i++)
                line(contour_rect,pts[i],pts[(i+1)%4],Scalar(0,0,255),3);

            //显示矩形
            namedWindow("最大边缘",WINDOW_AUTOSIZE);
            imshow("最大边缘",contour_rect);
        }
    }

    cout << "矩形角度" << angle <<endl;
    Point2f center(src.cols / 2, src.rows / 2);
    Mat rotm = getRotationMatrix2D(center, angle, 1.0);    //获取仿射变换矩阵
    Mat dst;
    warpAffine(src, dst, rotm, src.size(), INTER_LINEAR, 0, Scalar(255, 255, 255));    // 进行图像旋转操作
    //imwrite("123.png", dst);      //将校正后的图像保存下来
    imshow("Correct Image", dst);
}

程序运行效果

在这里插入图片描述


切边

将纠正后的图片存储下来,基本属于再次运行一遍代码,即可得到文档本身,得到去除白边的文档

#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <iostream>
#include <math.h>

using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;

#define PIC_PATH "/work/opencv_pic/"
#define PIC_NAME "case1.jpg"
int cur_val = 190;
int max_val = 255;

Mat src,gray_src,dst;
void find_roi(int,void*);
void cut_eg(int,void*);
int main (void)
{
    //获取完整的图片路径及名称
    string pic  =  string(PIC_PATH)+string(PIC_NAME);


    //打印图片路径
    cout << "pic  path is :"<<pic<<endl;

    //读取图片
    src  = imread(pic);

    //判断图片是否存在
    if(src.empty())
    {
        cout<<"pic is not exist!!!!"<<endl;
        return -1;
    }

    namedWindow("src pic",WINDOW_AUTOSIZE);
    imshow("src pic",src);


    //createTrackbar("阈值调整","src pic",&cur_val,max_val,find_roi);
    createTrackbar("阈值调整","src pic",&cur_val,max_val,cut_eg);

    //find_roi(0,0);
    cut_eg(0,0);
    waitKey(0);
    destroyAllWindows();
    return 0;
}
void find_roi(int,void*)
{
    cvtColor(src,gray_src,COLOR_BGR2GRAY);
    Mat canny_output;
    Canny(gray_src,canny_output,cur_val,cur_val*2,3);

    namedWindow("canny pic",WINDOW_AUTOSIZE);
    imshow("canny pic",canny_output);


    Mat recimg;
    vector<vector<Point>> contours;   //定义轮廓集合
    vector<Vec4i> hireachy;    //定义轮廓之间的索引
    //查找轮廓
    findContours(canny_output,contours,hireachy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point(0,0));

    //查找轮廓限制最基本范围  目测最大轮廓不应小于以下值
    int minW = src.cols*0.2;
    int minH = src.rows*0.55;
    dst = Mat::zeros(src.size(),CV_8UC3);
    float angle;   //轮廓外包矩形的旋转角度
    RotatedRect minrect;  //轮廓外包的最小矩形
    for(size_t i=0;i<contours.size();i++)
    {
        minrect = minAreaRect(contours[i]);  //获取轮廓外包矩形
        angle = minrect.angle;   //获取矩形角度
        if(minrect.size.width>minW && minrect.size.height > minH)  //判断矩形是不是那个比较大的
        {
            Point2f pts[4];    //矩形四个角点
            minrect.points(pts);   //获取角点
            Mat contour_rect = Mat::zeros(src.size(),CV_8UC3);

            //绘制外接最大矩形
            for(size_t i=0;i<4;i++)
                line(contour_rect,pts[i],pts[(i+1)%4],Scalar(0,0,255),3);

            //显示矩形
            namedWindow("最大边缘",WINDOW_AUTOSIZE);
            imshow("最大边缘",contour_rect);
        }
    }

    cout << "矩形角度" << angle <<endl;
    Point2f center(src.cols / 2, src.rows / 2);
    Mat rotm = getRotationMatrix2D(center, 0, 1.0);    //获取仿射变换矩阵
    Mat dst;
    warpAffine(src, dst, rotm, src.size(), INTER_LINEAR, 0, Scalar(255, 255, 255));    // 进行图像旋转操作
    //imwrite("123.png", dst);      //将校正后的图像保存下来
    imshow("Correct Image", dst);
}
void cut_eg(int,void*)
{
    cvtColor(src,gray_src,COLOR_BGR2GRAY);
    Mat canny_output;
    Canny(gray_src,canny_output,cur_val,cur_val*2,3);

    namedWindow("canny pic",WINDOW_AUTOSIZE);
    imshow("canny pic",canny_output);

    Mat recimg;
    vector<vector<Point>> contours;   //定义轮廓集合
    vector<Vec4i> hireachy;    //定义轮廓之间的索引
    //查找轮廓
    findContours(canny_output,contours,hireachy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point(0,0));

    //查找轮廓限制最基本范围  目测最大轮廓不应小于以下值
    int minW = src.cols*0.2;
    int minH = src.rows*0.5;
    dst = Mat::zeros(src.size(),CV_8UC3);
    RotatedRect minrect;  //轮廓外包的最小矩形
    Rect bbox;
    for(size_t i=0;i<contours.size();i++)
    {
        minrect = minAreaRect(contours[i]);  //获取轮廓外包矩形
        if(minrect.size.width>minW && minrect.size.height > minH)  //判断矩形是不是那个比较大的
        {
            Point2f pts[4];    //矩形四个角点
            minrect.points(pts);   //获取角点
            Mat contour_rect = Mat::zeros(src.size(),CV_8UC3);
            Mat vertices;       // 定义一个4行2列的单通道float类型的Mat,用来存储旋转矩形的四个顶点
            boxPoints(minrect, vertices);    // 计算旋转矩形的四个顶点坐标
            bbox = boundingRect(vertices);   //找到输入点集的最小外包直立矩形,返回Rect类型

            //绘制外接最大矩形
            for(size_t i=0;i<4;i++)
                line(contour_rect,pts[i],pts[(i+1)%4],Scalar(0,0,255),3);

            //显示矩形
            namedWindow("最大边缘",WINDOW_AUTOSIZE);
            imshow("最大边缘",contour_rect);
        }
    }

    if (bbox.width > 0 && bbox.height > 0 )
    {
        Mat roiImg = src(bbox);        //从原图中截取兴趣区域
        namedWindow("roi_win", WINDOW_AUTOSIZE);
        imshow("roi_win", roiImg);
    }

}

程序运行效果

在这里插入图片描述

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐