问题描述:提取一幅图像中的最大矩形区域。
注意:图像可能是倾斜的,要先进行旋转校正。
代码实现主要分为两块:一是实现图像旋转校正;一是实现提取目标矩形区域。
旋转校正代码实现
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;
}