透视变换 图像校正

学习

Posted by simplex on June 7, 2021

透视变换 图像校正

1任务

输入一张图像,将图像的目标区(四边形)域通过透视变换,变换到正确的形状。

2 原理

透视变换的本质是将图像投影到一个新的视平面

图像校正所做的事情就是求出一个透视变换矩阵,使得选定的目标区域,变换到固定的一个区域比如在我的代码中为一个对角为(0, 0),(500, 500)的矩形内。

这是一个二维坐标到二维坐标的变换。有 \(\left[ \begin{matrix} x' \\ y' \\ w \end{matrix} \right] = \left[ \begin{matrix} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & 1 \end{matrix} \right] \left[ \begin{matrix} x \\ y \\ 1 \end{matrix} \right]\) 然后其次坐标到二维坐标\(({ {x'}\over w}, { {y'}\over w})\)

考虑的这样的变换要用到齐次坐标,所以

这里面有8个参数,所以需要四个点,构成方程,解方程得到变换矩阵

点的选择就是目标区域中的四个角上的点,只要得到这四个点,就可以得到变换矩阵,完成校正

3 点的选择

  • 通过霍夫变换进行直线检测,得到直线的近似多角曲线,如果是如果曲线有四个点,则为需要变换的四个点,如果不是四个点,则采用第二个策略
  • 手动选择4个点

4 代码实现

  • 使用语言 c++

  • 环境:Windows Visual Studio 2017 x64 Release msvc

  • 借助opencv2 库,以及一些基本的C++的库

    ​ 流程图

    lex=>start: 输入图片
    grey=>operation: 灰度化
    blur=>operation: 边缘处理得到边缘图像
    hough=>operation: 霍夫变换进行直线检测
    ploy=>operation: 取直线的交点,用得到交点的近似多边形
      
    judg1=>condition: 是否是四边形
    pick=>operation: 手动选择4个点
    trans=>operation: 根据这四个点,计算透视矩阵
    output=>operation: 输出校正后的图像
    e=>end: 结束
      
    lex->grey->blur->hough->ploy->judg1
    judg1(yes)->trans->output->e
    judg1(no)->pick->trans
      
    

    5 实验结果

    转变前(手动选点) 转变后(手动选点)
    org cgb1
    转变前(自动选点) 转变后(自动选点)
    card card1

    6 源代码

#include <opencv2/imgproc/imgproc.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2\imgproc\types_c.h>
#include <iostream>  
#include <stdio.h>
#include <string>
#include <cstring>
     

using namespace cv;
using namespace std;

Point2f center(0, 0);
float eps = 1e-6;

int tranS();
// 求两条直线的交点
Point2f computeIntersect(Vec4i a, Vec4i b)
{
	int x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3], x3 = b[0], y3 = b[1], x4 = b[2], y4 = b[3];
	float denom, d;

	if (abs(d = ((float)(x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4))) > eps)
	{
		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;
	}
	else
		return Point2f(-1, -1);
}

// 将选定的4个点分到对应的四个顶点
void sortCorners(vector<Point2f>& corners,
	Point2f center)
{
	vector<Point2f> top, bot;

	for (int i = 0; i < corners.size(); i++)
	{
		
		if (corners[i].y < center.y)
			top.push_back(corners[i]);
		else
			bot.push_back(corners[i]);
	}
	corners.clear();

	if (top.size() == 2 && bot.size() == 2) {
		Point2f tl = top[0].x > top[1].x ? top[1] : top[0];
		Point2f tr = top[0].x > top[1].x ? top[0] : top[1];
		Point2f bl = bot[0].x > bot[1].x ? bot[1] : bot[0];
		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);
		for (auto & p : corners)
		{
			printf("%f  %f\n", p.x, p.y);
		}
	}
}

int clickTimes = 0;
Mat tmpsw, src;
vector<Vec4i> lines;
vector<Point2f> corners;
string  file_name;

// 读取四个鼠标选取的点
void get4Point(int event, int x, int y, int flags, void *utsc)
{

	if (event == EVENT_LBUTTONUP)   //响应鼠标左键抬起事件
	{
		circle(tmpsw, Point(x, y), 2.5, Scalar(0, 0, 255), 2.5);  //标记选中点
		imshow("Source Image", tmpsw);
		corners.push_back(Point2f(x, y));
		clickTimes++;
		printf("size %d\n", corners.size());
	}
	if (event == EVENT_LBUTTONUP && clickTimes == 4)
	{
		destroyWindow("Source Image");
		tranS();
	}
}

// 通过得到的四个点,得到透视变换,得到透视变换后的图片
int tranS()
{
	// Get mass center  
	for (int i = 0; i < corners.size(); i++)
		center += corners[i];
	center *= (1. / corners.size());

	sortCorners(corners, center);
	if (corners.size() != 4) {
		cout << "The corners were not sorted correctly! corner size:"<<corners.size() << endl;
		return -1;
	}
	Mat dst = src.clone();

	// Draw lines  
	//for (int i = 0; i < lines.size(); i++)
	//{
	//	Vec4i v = lines[i];
	//	line(dst, Point(v[0], v[1]), Point(v[2], v[3]), CV_RGB(0, 255, 0));
	//}

	// Draw corner points  
	circle(dst, corners[0], 3, CV_RGB(255, 0, 0), 2);
	circle(dst, corners[1], 3, CV_RGB(0, 255, 0), 2);
	circle(dst, corners[2], 3, CV_RGB(0, 0, 255), 2);
	circle(dst, corners[3], 3, CV_RGB(255, 255, 255), 2);

	// Draw mass center  
	circle(dst, center, 3, CV_RGB(255, 255, 0), 2);

	Mat quad = Mat::zeros(500, 500, CV_8UC3);

	vector<Point2f> quad_pts;
	quad_pts.push_back(Point2f(0, 0));
	quad_pts.push_back(Point2f(quad.cols, 0));
	quad_pts.push_back(Point2f(quad.cols, quad.rows));
	quad_pts.push_back(Point2f(0, quad.rows));

	Mat transmtx = getPerspectiveTransform(corners, quad_pts);
	warpPerspective(src, quad, transmtx, quad.size());
	cout << transmtx << endl;
	namedWindow(file_name, WINDOW_NORMAL);
	resizeWindow(file_name, 800, 600);
	imshow(file_name, dst);
	imshow("quadrilateral", quad);
	cout << "in func" << endl;
	waitKey();
	return 0;
}

int main()
{
	file_name = "org.jpg";
	src = imread(file_name);
	if (src.empty())
		return -1;
	
	Mat bw;
	tmpsw = src.clone();
	// 载入图像→灰度化→边缘处理得到边缘图像
	cvtColor(src, bw, CV_BGR2GRAY);
	blur(bw, bw, Size(3, 3));
	Canny(bw, bw, 100, 100, 3);
	//Canny(bw, bw, 100, 150, 3);

	HoughLinesP(bw, lines, 1, CV_PI / 180, 70, 30, 10);

	// Expand the lines  
	for (int i = 0; i < lines.size(); i++)
	{
		Vec4i v = lines[i];
		lines[i][0] = 0;
		lines[i][1] = ((float)v[1] - v[3]) / (v[0] - v[2]) * -v[0] + v[1];
		lines[i][2] = src.cols;
		lines[i][3] = ((float)v[1] - v[3]) / (v[0] - v[2]) * (src.cols - v[2]) + v[3];
	}

	for (int i = 0; i < lines.size(); i++)
	{
		for (int j = i + 1; j < lines.size(); j++)
		{
			Point2f pt = computeIntersect(lines[i], lines[j]);
			if (pt.x >= 0 && pt.y >= 0)
				corners.push_back(pt);
		}
	}

	vector<Point2f> approx;
	approxPolyDP(Mat(corners), approx, arcLength(Mat(corners), true) * 0.02, true);

	if (approx.size() != 4)
	{
		cout << "The object is not quadrilateral!" << endl;
		//imshow(file_name, tmpsw);
		corners.clear();
		namedWindow("Source Image", WINDOW_NORMAL);
		resizeWindow("Source Image", 800, 600);
		imshow("Source Image", tmpsw);
		setMouseCallback("Source Image", get4Point);
	}
	else
	{
		tranS();
	}
	waitKey();
	return 0;
}