C++ OpenCV實戰之圖像透視矯正

前言

本文將使用OpenCV C++ 進行圖像透視矯正。

一、圖像預處理

原圖如圖所示。首先進行圖像預處理。將圖像進行灰度、濾波、二值化、形態學等操作,目的是為瞭下面的輪廓提取。在這裡我還使用瞭形態學開、閉操作,目的是使整個二值圖像連在一起。大傢在做圖像預處理時,可以根據圖像特征自行處理。

	Mat gray;
	cvtColor(src, gray, COLOR_BGR2GRAY);

	Mat gaussian;
	GaussianBlur(gray, gaussian, Size(3, 3), 0);

	Mat thresh;
	threshold(gaussian, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
	Mat open;
	morphologyEx(thresh, open, MORPH_OPEN, kernel);

	Mat kernel1 = getStructuringElement(MORPH_RECT, Size(7, 7));
	Mat close;
	morphologyEx(open, close, MORPH_CLOSE, kernel1);

如圖就是經過圖像預處理得到的二值圖像。

二、輪廓提取

1.提取最外輪廓

	vector<vector<Point>>contours;	
	findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

使用findContours、RETR_EXTERNAL就可以提取出物體最外輪廓。

2.提取矩形四個角點

接下來將使用approxPolyDP進行多邊形輪廓擬合,目的是為瞭找到矩形的四個角點。關於approxPolyDP API大傢可以自行百度查看其用法。

	vector<vector<Point>>conPoly(contours.size());
	vector<Point>srcPts;

	for (int i = 0; i < contours.size(); i++)
	{
		double area = contourArea(contours[i]);

		if (area > 10000)
		{
			double peri = arcLength(contours[i], true);

			approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);
             //獲取矩形四個角點
			srcPts = { conPoly[i][0],conPoly[i][1],conPoly[i][2],conPoly[i][3] };

		}

	}

3.將矩形角點排序

由於我們之前使用的approxPolyDP獲取的角點是無序的,所以我們得確定各角點所在的位置。在這裡我使用的算法是根據其角點所在圖像位置特征確定左上、左下、右下、右上四個點。

	int width = src.cols / 2;
	int height = src.rows / 2;
	int T_L, T_R, B_R, B_L;

	for (int i = 0; i < srcPts.size(); i++)
	{
		if (srcPts[i].x < width && srcPts[i].y < height)
		{
			T_L = i;
		}
		if (srcPts[i].x > width && srcPts[i].y < height)
		{
			T_R = i;
		}
		if (srcPts[i].x > width && srcPts[i].y > height)
		{
			B_R = i;
		}
		if (srcPts[i].x < width && srcPts[i].y > height)
		{
			B_L = i;
		}
		
	}

如圖所示。至此已經完成瞭矩形四個角點的定位。接下來就可以使用透視變換進行圖像矯正瞭。

三、透視矯正

在這裡我們需要知道透視變換一個原理:

變換後,圖像的長和寬應該變為:

長 = max(變換前左邊長,變換前右邊長)

寬 = max(變換前上邊長,變換前下邊長)

設變換後圖像的左上角位置為原點位置。

	double LeftHeight = EuDis(srcPts[T_L], srcPts[B_L])	
	double RightHeight = EuDis(srcPts[T_R], srcPts[B_R]);
	double MaxHeight = max(LeftHeight, RightHeight);

	double UpWidth = EuDis(srcPts[T_L], srcPts[T_R]);
	double DownWidth = EuDis(srcPts[B_L], srcPts[B_R]);
	double MaxWidth = max(UpWidth, DownWidth);

確定變換後的長寬之後,就可以使用getPerspectiveTransform、warpPerspective進行透視矯正瞭。

//這裡使用的順序是左上、右上、右下、左下順時針順序。SrcAffinePts、DstAffinePts要一一對應
	Point2f SrcAffinePts[4] = { Point2f(srcPts[T_L]),Point2f(srcPts[T_R]) ,Point2f(srcPts[B_R]) ,Point2f(srcPts[B_L]) };
	Point2f DstAffinePts[4] = { Point2f(0,0),Point2f(MaxWidth,0),Point2f(MaxWidth,MaxHeight),Point2f(0,MaxHeight) };

	Mat M = getPerspectiveTransform(SrcAffinePts, DstAffinePts);
	
	Mat DstImg;
	warpPerspective(src, DstImg, M, Point(MaxWidth, MaxHeight));

這就是進行透視矯正之後的效果。

四、源碼

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

using namespace std;
using namespace cv;

double EuDis(Point pt1, Point pt2)
{
	return sqrt((pt2.x - pt1.x)*(pt2.x - pt1.x) + (pt2.y - pt1.y)*(pt2.y - pt1.y));
}

int main()
{

	Mat src = imread("1.jpg");
	if (src.empty())
	{
		cout << "No Image!" << endl;
		system("pause");
		return -1;
	}

	Mat gray;
	cvtColor(src, gray, COLOR_BGR2GRAY);

	Mat gaussian;
	GaussianBlur(gray, gaussian, Size(3, 3), 0);

	Mat thresh;
	threshold(gaussian, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
	Mat open;
	morphologyEx(thresh, open, MORPH_OPEN, kernel);

	Mat kernel1 = getStructuringElement(MORPH_RECT, Size(7, 7));
	Mat close;
	morphologyEx(open, close, MORPH_CLOSE, kernel1);

	vector<vector<Point>>contours;	
	findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

	vector<vector<Point>>conPoly(contours.size());
	vector<Point>srcPts;

	for (int i = 0; i < contours.size(); i++)
	{
		double area = contourArea(contours[i]);

		if (area > 10000)
		{
			double peri = arcLength(contours[i], true);

			approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);

			srcPts = { conPoly[i][0],conPoly[i][1],conPoly[i][2],conPoly[i][3] };

		}

	}

	int width = src.cols / 2;
	int height = src.rows / 2;
	int T_L, T_R, B_R, B_L;

	for (int i = 0; i < srcPts.size(); i++)
	{
		if (srcPts[i].x < width && srcPts[i].y < height)
		{
			T_L = i;
		}
		if (srcPts[i].x > width && srcPts[i].y < height)
		{
			T_R = i;
		}
		if (srcPts[i].x > width && srcPts[i].y > height)
		{
			B_R = i;
		}
		if (srcPts[i].x < width && srcPts[i].y > height)
		{
			B_L = i;
		}
		
	}

	//circle(src, srcPts[T_L], 10, Scalar(0, 0, 255), -1);
	//circle(src, srcPts[T_R], 10, Scalar(0, 255, 255), -1);
	//circle(src, srcPts[B_R], 10, Scalar(255, 0, 0), -1);
	//circle(src, srcPts[B_L], 10, Scalar(0, 255, 0), -1);

	/*
	變換後,圖像的長和寬應該變為:
	長 = max(變換前左邊長,變換前右邊長)
	寬 = max(變換前上邊長,變換前下邊長)
	設變換後圖像的左上角位置為原點位置。
	*/

	double LeftHeight = EuDis(srcPts[T_L], srcPts[B_L]);
	double RightHeight = EuDis(srcPts[T_R], srcPts[B_R]);
	double MaxHeight = max(LeftHeight, RightHeight);

	double UpWidth = EuDis(srcPts[T_L], srcPts[T_R]);
	double DownWidth = EuDis(srcPts[B_L], srcPts[B_R]);
	double MaxWidth = max(UpWidth, DownWidth);

	Point2f SrcAffinePts[4] = { Point2f(srcPts[T_L]),Point2f(srcPts[T_R]) ,Point2f(srcPts[B_R]) ,Point2f(srcPts[B_L]) };
	Point2f DstAffinePts[4] = { Point2f(0,0),Point2f(MaxWidth,0),Point2f(MaxWidth,MaxHeight),Point2f(0,MaxHeight) };

	Mat M = getPerspectiveTransform(SrcAffinePts, DstAffinePts);

	Mat DstImg;
	warpPerspective(src, DstImg, M, Point(MaxWidth, MaxHeight));
	//imshow("Dst", DstImg);


	imshow("src", src);
	waitKey(0);
	destroyAllWindows();

	system("pause");
	return 0;
}

總結

本文使用OpenCV C++ 進行圖像透視矯正,關鍵步驟有以下幾點。

1、圖像預處理,獲取二值圖像。

2、將二值圖像進行輪廓提取,定位矩形四個角點,並確定其位置。

3、確定圖像變換後的長、寬。並將SrcAffinePts、DstAffinePts一一對應之後進行透視變換。

到此這篇關於C++ OpenCV實戰之圖像透視矯正的文章就介紹到這瞭,更多相關C++ OpenCV圖像透視矯正內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: