C++ OpenCV實(shí)戰(zhàn)之圖像透視矯正
前言
本文將使用OpenCV C++ 進(jìn)行圖像透視矯正。
一、圖像預(yù)處理

原圖如圖所示。首先進(jìn)行圖像預(yù)處理。將圖像進(jìn)行灰度、濾波、二值化、形態(tài)學(xué)等操作,目的是為了下面的輪廓提取。在這里我還使用了形態(tài)學(xué)開、閉操作,目的是使整個(gè)二值圖像連在一起。大家在做圖像預(yù)處理時(shí),可以根據(jù)圖像特征自行處理。
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);

如圖就是經(jīng)過圖像預(yù)處理得到的二值圖像。
二、輪廓提取
1.提取最外輪廓
vector<vector<Point>>contours; findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
使用findContours、RETR_EXTERNAL就可以提取出物體最外輪廓。
2.提取矩形四個(gè)角點(diǎn)
接下來將使用approxPolyDP進(jìn)行多邊形輪廓擬合,目的是為了找到矩形的四個(gè)角點(diǎn)。關(guān)于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);
//獲取矩形四個(gè)角點(diǎn)
srcPts = { conPoly[i][0],conPoly[i][1],conPoly[i][2],conPoly[i][3] };
}
}
3.將矩形角點(diǎn)排序
由于我們之前使用的approxPolyDP獲取的角點(diǎn)是無序的,所以我們得確定各角點(diǎn)所在的位置。在這里我使用的算法是根據(jù)其角點(diǎn)所在圖像位置特征確定左上、左下、右下、右上四個(gè)點(diǎn)。
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;
}
}

如圖所示。至此已經(jīng)完成了矩形四個(gè)角點(diǎn)的定位。接下來就可以使用透視變換進(jìn)行圖像矯正了。
三、透視矯正
在這里我們需要知道透視變換一個(gè)原理:
變換后,圖像的長(zhǎng)和寬應(yīng)該變?yōu)椋?/p>
長(zhǎng) = max(變換前左邊長(zhǎng),變換前右邊長(zhǎng))
寬 = max(變換前上邊長(zhǎng),變換前下邊長(zhǎng))
設(shè)變換后圖像的左上角位置為原點(diǎn)位置。
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);
確定變換后的長(zhǎng)寬之后,就可以使用getPerspectiveTransform、warpPerspective進(jìn)行透視矯正了。
//這里使用的順序是左上、右上、右下、左下順時(shí)針順序。SrcAffinePts、DstAffinePts要一一對(duì)應(yīng)
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));

這就是進(jìn)行透視矯正之后的效果。
四、源碼
#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);
/*
變換后,圖像的長(zhǎng)和寬應(yīng)該變?yōu)椋?
長(zhǎng) = max(變換前左邊長(zhǎng),變換前右邊長(zhǎng))
寬 = max(變換前上邊長(zhǎng),變換前下邊長(zhǎng))
設(shè)變換后圖像的左上角位置為原點(diǎn)位置。
*/
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;
}
總結(jié)
本文使用OpenCV C++ 進(jìn)行圖像透視矯正,關(guān)鍵步驟有以下幾點(diǎn)。
1、圖像預(yù)處理,獲取二值圖像。
2、將二值圖像進(jìn)行輪廓提取,定位矩形四個(gè)角點(diǎn),并確定其位置。
3、確定圖像變換后的長(zhǎng)、寬。并將SrcAffinePts、DstAffinePts一一對(duì)應(yīng)之后進(jìn)行透視變換。
到此這篇關(guān)于C++ OpenCV實(shí)戰(zhàn)之圖像透視矯正的文章就介紹到這了,更多相關(guān)C++ OpenCV圖像透視矯正內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
epoll多路復(fù)用的一個(gè)實(shí)例程序(C實(shí)現(xiàn))
這篇文章主要為大家詳細(xì)介紹了epoll多路復(fù)用的一個(gè)實(shí)例程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
C語言實(shí)現(xiàn)統(tǒng)計(jì)100以內(nèi)所有素?cái)?shù)的個(gè)數(shù)
本文詳細(xì)講解了C語言實(shí)現(xiàn)統(tǒng)計(jì)100以內(nèi)所有素?cái)?shù)個(gè)數(shù)的方法,文中通過示例代碼介紹的非常詳細(xì)。需要的朋友可以收藏下,方便下次瀏覽觀看2021-11-11
C語言循環(huán)結(jié)構(gòu)與時(shí)間函數(shù)用法實(shí)例教程
這篇文章主要介紹了C語言循環(huán)結(jié)構(gòu)與時(shí)間函數(shù)用法,是C語言中非常重要的一個(gè)技巧,需要的朋友可以參考下2014-08-08
詳解C語言中telldir()函數(shù)和seekdir()函數(shù)的用法
這篇文章主要介紹了詳解C語言中telldir()函數(shù)和seekdir()函數(shù)的用法,是C語言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09

