opencv案例之图片透视矫正

案例背景

拍摄或者扫描图像不是规则的矩形,会对后期处理产生不好的影响,需要通过透视变换矫正得到正确的形状


方法

  • 二值化
  • 形态学操作,去噪点
  • 进行轮廓查找, 通过 矩形的长款过滤较小和图片的大边框
  • 霍夫直线变换,查找直线
  • 过滤直线,通过直线位置和长度确定上下左右四条直线
  • 求出四条直线
  • 得到四条直线的交点,这就是物体原始四个角点
  • 把原始的四个角点,变换到图片的四个角落,透视变换会把相对位置的像素通过线性插值填充

相关api

计算透视变换函数api

Mat cv::getPerspectiveTransform 	( 	
        InputArray  	src,
		InputArray  	dst,
		int  	solveMethod = DECOMP_LU 
	) 	

参数说明

  • src:源图像中待测矩形的四点坐标
  • dst:目标图像中矩形的四点坐标
  • 返回由源图像中矩形到目标图像矩形变换的矩阵

使用变换函数纠正函数api

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

参数说明

  • src:输入图像
  • dst:输出图像
  • M:变换矩阵
  • dsize:目标图像shape
  • flags:插值方式,interpolation方法INTER_LINEARINTER_NEAREST,与可选标志WARP_INVERSE_MAP的组合,将M设置为逆变换(dst→src)。
  • borderMode:边界补偿方式,BORDER_CONSTANT or BORDER_REPLICATE
  • borderValue:边界补偿大小,常值,默认为0

代码

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

using namespace std;
using namespace cv;


#define PIC_PATH "/work/opencv_pic/"
#define PIC_NAME "case5.png"

int main(void)
{
    Mat src,gray_src;

    //获取完整的图片路径及名称
    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);

    cvtColor(src,gray_src,COLOR_BGR2GRAY);

    //图片与背景有巨大的反差 先将图片二值化 方便提取主体部分
    Mat binaryimg;
    threshold(gray_src,binaryimg,0,255,THRESH_BINARY | THRESH_OTSU);
    //imshow("binaryimg",binaryimg);

    //图片反向 进行去噪
    bitwise_not(binaryimg,binaryimg);
    //imshow("bitnot binaryimg",binaryimg);

    //主题部分内部仍有黑色噪点干扰存在  闭操作
    Mat kernel = getStructuringElement(MORPH_RECT,Size(3,3),Point(-1,-1));
    morphologyEx(binaryimg,binaryimg,MORPH_CLOSE,kernel,Point(-1,-1),3);
    //imshow("close opera binaryimg",binaryimg);

    //查找轮廓 绘制轮廓
    vector<vector<Point>> contours;
    findContours(binaryimg,contours,RETR_TREE,CHAIN_APPROX_SIMPLE);
    Mat contourimg = Mat::zeros(src.size(),src.type());
    for(size_t i=0;i<contours.size();i++)
    {
        Rect rect = boundingRect(contours[i]);
        //通过轮廓外界矩形大小 滤除国徽部分干扰
        if(rect.width > src.cols/2 && rect.height > src.rows/2)
            drawContours(contourimg,contours,i,Scalar(0,0,255),2);
    }
    imshow("contourimg",contourimg);

    int height = src.rows;
    int width = src.cols;
    //霍夫直线检测
    Mat hough;
    //霍夫检测单通道图
    cvtColor(contourimg,hough,COLOR_BGR2GRAY);
    vector<Vec4i> lines;
    double accu = min(height*0.5,width*0.5);
    HoughLinesP(hough,lines,1,CV_PI/180.0,accu,accu,0);


    //绘制直线
    Mat lineimg = Mat::zeros(src.size(),src.type());
    for(size_t i=0;i<lines.size();i++)
    {
        Vec4i linep = lines[i];
        line(lineimg,Point(linep[0],linep[1]),Point(linep[2],linep[3]),Scalar(0,0,255),2,8);
    }
    imshow("hough line img",lineimg);


    //判断四条线的位置 上下左右
    // 定位直线
    int deltah = 0;
    Vec4i topLine, bottomLine, leftLine, rightLine;
    for (size_t i = 0; i < lines.size(); i++){
        Vec4i ln = lines[i];
        deltah = abs(ln[3] - ln[1]);  //直线两点之间的竖直差
        if (ln[3] < height / 2.0 && ln[1] < height / 2.0 && deltah < accu - 1){
            topLine = lines[i];
        }
        if (ln[3] > height / 2.0 && ln[1] > height / 2.0 && deltah < accu - 1){
            bottomLine = lines[i];
        }
        if (ln[0] < width / 2.0 && ln[2] < width / 2.0 ){
            leftLine = lines[i];
        }
        if (ln[0] > width / 2.0 && ln[2] > width / 2.0){
            rightLine = lines[i];
        }
    }
    cout << "topLine : " << topLine << endl;
    cout << "bottomLine : " << bottomLine << endl;
    cout << "leftLine : " << leftLine << endl;
    cout << "rightLine : " << rightLine << endl;

    // 拟合四条直线方程
    float k1, c1;
    k1 = float(topLine[3] - topLine[1]) / float(topLine[2] - topLine[0]);
    c1 = topLine[1] - k1*topLine[0];
    float k2, c2;
    k2 = float(bottomLine[3] - bottomLine[1]) / float(bottomLine[2] - bottomLine[0]);
    c2 = bottomLine[1] - k2*bottomLine[0];
    float k3, c3;
    k3 = float(leftLine[3] - leftLine[1]) / float(leftLine[2] - leftLine[0]);
    c3 = leftLine[1] - k3*leftLine[0];
    float k4, c4;
    k4 = float(rightLine[3] - rightLine[1]) / float(rightLine[2] - rightLine[0]);
    c4 = rightLine[1] - k4*rightLine[0];

    // 四条直线交点
    Point p1; // 左上角
    p1.x = static_cast<int>((c1 - c3) / (k3 - k1));
    p1.y = static_cast<int>(k1*p1.x + c1);
    Point p2; // 右上角
    p2.x = static_cast<int>((c1 - c4) / (k4 - k1));
    p2.y = static_cast<int>(k1*p2.x + c1);
    Point p3; // 左下角
    p3.x = static_cast<int>((c2 - c3) / (k3 - k2));
    p3.y = static_cast<int>(k2*p3.x + c2);
    Point p4; // 右下角
    p4.x = static_cast<int>((c2 - c4) / (k4 - k2));
    p4.y = static_cast<int>(k2*p4.x + c2);
    cout << "p1(x, y)=" << p1.x << "," << p1.y << endl;
    cout << "p2(x, y)=" << p2.x << "," << p2.y << endl;
    cout << "p3(x, y)=" << p3.x << "," << p3.y << endl;
    cout << "p4(x, y)=" << p4.x << "," << p4.y << endl;

    // 显示四个点坐标
    circle(lineimg, p1, 2, Scalar(0,255, 0), 2, 8, 0);
    circle(lineimg, p2, 2, Scalar(0,255, 0), 2, 8, 0);
    circle(lineimg, p3, 2, Scalar(0,255, 0), 2, 8, 0);
    circle(lineimg, p4, 2, Scalar(0,255, 0), 2, 8, 0);
    //line(lineimg, Point(topLine[0], topLine[1]), Point(topLine[2], topLine[3]), Scalar(0, 255, 0), 2, 8, 0);
    imshow("four corners", lineimg);

    // 透视变换
    vector<Point2f> src_corners(4); // 原来的点
    src_corners[0] = p1;
    src_corners[1] = p2;
    src_corners[2] = p3;
    src_corners[3] = p4;

    vector<Point2f> dst_corners(4); // 目标点位
    dst_corners[0] = Point(0,0);
    dst_corners[1] = Point(width, 0);
    dst_corners[2] = Point(0, height);
    dst_corners[3] = Point(width , height);

    // 获取变换矩阵
    Mat reslutImg;

    //获取透视转换矩阵
    Mat warpmatrix = getPerspectiveTransform(src_corners, dst_corners);
    //将原图通过转换矩阵矫正
    warpPerspective(src, reslutImg, warpmatrix, reslutImg.size(), INTER_LINEAR);
    imshow("dst", reslutImg);

    waitKey(0);
    destroyAllWindows();
    return 0;

}

效果演示

在这里插入图片描述

  • 1
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值