問題描述:提取一幅圖像中的最大矩形區域。
注意:圖像可能是傾斜的,要先進行旋轉校正。
代碼實現主要分爲兩塊:一是實現圖像旋轉校正;一是實現提取目標矩形區域。
旋轉校正代碼實現
Mat correctImg(Mat src)
{
Mat gray, gauss;
cvtColor(src, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, gauss, Size(5, 5), 0, 0);
//canny檢測
Mat edge;
Canny(gauss, edge, 80, 240, 3);
//直線檢測
vector<Vec4i>lines;
HoughLinesP(edge, lines, 1, 3.1415 / 180, 200, 100, 0);
vector<double>slope;
for (size_t i = 0; i < lines.size(); i++)
{
//計算斜率
double k = (lines[i][3] - lines[i][1]) / (lines[i][2] - lines[i][0] + 0.00001);
if (k >= 0)
{
slope.push_back(k);
}
cout << "斜率:" << k << endl;
}
//計算圖像旋轉角度
double mean_k = accumulate(slope.begin(), slope.end(), 0.0) / slope.size();
cout << "平均斜率:" << mean_k << endl;
double angle = atan2(accumulate(slope.begin(), slope.end(), 0.0), slope.size()) * 180 / 3.14;
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));
imshow("correct", dst);
imwrite("correct.jpg", dst);
return dst;
}
代碼解釋:
(1)霍夫曼直線檢測算法在opencv裏有兩個api函數,比較常用的是這個HoughLinesP函數,因爲這個函數的返回值是直線兩個端點的座標,比較符合使用習慣。另一個函數HoughLines的返回值是直線的兩個極座標參數。
(2)for循環中爲什麼使用size_t定義而不是int,size_t是unsigned int,比int更加穩定。
(3)計算斜率時爲什麼分母上加個0.00001,防止分母爲0,產生數據溢出。
(4)利用反正切函數atan2計算出的是弧度,還應乘以180°/Π纔是真正的角度。
提取目標矩形的代碼實現
void findROI(Mat image, Mat dst)
{
Mat gray, gauss, edge;
cvtColor(image, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, gauss, Size(5, 5), 0, 0);
Canny(gauss, edge, 80, 240, 3);
//尋找輪廓
vector<vector<Point>>contours;
vector<Vec4i>hie;
findContours(edge, contours, hie, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
//定義最小矩形
int minW = 0.5*image.cols;
int minH = 0.5*image.rows;
Mat drawImage = Mat::zeros(image.size(), CV_8UC3);
Rect bbox;
cout << "image的寬:" << image.cols << "," << "image的高:" << image.rows << endl;
for (size_t i = 0; i < contours.size(); i++)
{
//尋找最小外接矩形
RotatedRect minRect = minAreaRect(contours[i]);
cout << "minRect的寬:" << minRect.size.width << "," << "minRect的高:" << minRect.size.height << endl;
//如果最小外接矩形大於定義的最小矩形尺寸,則符合條件
if (minRect.size.width > minW && minRect.size.height > minH && minRect.size.width < (image.cols - 5))
{
Point2f pts[4];
minRect.points(pts);
bbox = minRect.boundingRect();
for (int j = 0; j < 4; j++)
{
//畫出模板矩形
line(drawImage, pts[j], pts[(j + 1) % 4], Scalar(0, 0, 255), 2, 8, 0);
}
}
}
imshow("drawImage", drawImage);
//bbox的長寬大於0則表明找到了矩形區域
if (bbox.width > 0 && bbox.height > 0)
{
dst = image(bbox);
imshow("輪廓", dst);
imwrite("roi.jpg", dst);
}
return;
}
代碼解釋:
(1)定義一個最小矩形尺寸,爲了過濾圖片中較小輪廓的干擾。
(2)在畫矩形窗口模板時,(pts+1)%4是爲了防止數據溢出。
(3)如果圖像輪廓大於定義的最小矩形尺寸,則說明符合要求,這個最小矩形尺寸則是看情況指定的,還有一個最小外接矩形的寬不小於圖像寬-5,這是爲了防止圖像輪廓不至於太過誇張,當然這種情況很少出現。
運行結果:
完整代碼:
#include<opencv.hpp>
#include<iostream>
#include<numeric>
using namespace std;
using namespace cv;
Mat correctImg(Mat src);//圖像校正
void findROI(Mat image, Mat dst);//尋找目標區域
int main()
{
//加載圖像
Mat src = imread("E:\\open CV\\VS\\project\\切邊\\切邊\\1.jpg");
if (src.empty())
{
cout << "no image!" << endl;
return -1;
}
imshow("src", src);
Mat dst = correctImg(src);
Mat roi;
findROI(dst, roi);
waitKey(0);
return 0;
}
Mat correctImg(Mat src)
{
Mat gray, gauss;
cvtColor(src, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, gauss, Size(5, 5), 0, 0);
//canny檢測
Mat edge;
Canny(gauss, edge, 80, 240, 3);
//直線檢測
vector<Vec4i>lines;
HoughLinesP(edge, lines, 1, 3.1415 / 180, 200, 100, 0);
vector<double>slope;
for (size_t i = 0; i < lines.size(); i++)
{
//計算斜率
double k = (lines[i][3] - lines[i][1]) / (lines[i][2] - lines[i][0] + 0.00001);
if (k >= 0)
{
slope.push_back(k);
}
cout << "斜率:" << k << endl;
}
//計算圖像旋轉角度
double mean_k = accumulate(slope.begin(), slope.end(), 0.0) / slope.size();
cout << "平均斜率:" << mean_k << endl;
double angle = atan2(accumulate(slope.begin(), slope.end(), 0.0), slope.size()) * 180 / 3.14;
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));
imshow("correct", dst);
imwrite("correct.jpg", dst);
return dst;
}
void findROI(Mat image, Mat dst)
{
Mat gray, gauss, edge;
cvtColor(image, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, gauss, Size(5, 5), 0, 0);
Canny(gauss, edge, 80, 240, 3);
//尋找輪廓
vector<vector<Point>>contours;
vector<Vec4i>hie;
findContours(edge, contours, hie, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
//定義最小矩形
int minW = 0.5*image.cols;
int minH = 0.5*image.rows;
Mat drawImage = Mat::zeros(image.size(), CV_8UC3);
Rect bbox;
cout << "image的寬:" << image.cols << "," << "image的高:" << image.rows << endl;
for (size_t i = 0; i < contours.size(); i++)
{
//尋找最小外接矩形
RotatedRect minRect = minAreaRect(contours[i]);
cout << "minRect的寬:" << minRect.size.width << "," << "minRect的高:" << minRect.size.height << endl;
//如果最小外接矩形大於定義的最小矩形尺寸,則符合條件
if (minRect.size.width > minW && minRect.size.height > minH && minRect.size.width < (image.cols - 5))
{
Point2f pts[4];
minRect.points(pts);
bbox = minRect.boundingRect();
for (int j = 0; j < 4; j++)
{
//畫出模板矩形
line(drawImage, pts[j], pts[(j + 1) % 4], Scalar(0, 0, 255), 2, 8, 0);
}
}
}
imshow("drawImage", drawImage);
//bbox的長寬大於0則表明找到了矩形區域
if (bbox.width > 0 && bbox.height > 0)
{
dst = image(bbox);
imshow("輪廓", dst);
imwrite("roi.jpg", dst);
}
return;
}