一、背景
现有如下图片,希望能用鼠标画出矩形,在矩形中计算出图片的倾斜角度,并由此自动旋转使图片水平。
二、实现
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
using namespace cv;
// 全局变量
Mat g_image_original, g_image_gray, g_image_rect; // 原始图片,灰度图,鼠标画出的矩形图片
Rect g_rect; // 鼠标画出的矩形的坐标
Vec4d g_max_line; // g_rect的最长的线段,用于计算g_angle
double g_angle; // 图片旋转角度
// 鼠标
size_t g_step = 0; // 操作步骤的标志
// 进度条
int g_Canny_th = 255, g_HoughLinesP_th = 0, g_HoughLinesP_ml = 0, g_HoughLinesP_mg = 0;
// 画图函数
void draw(const string name, const Mat img)
{
Mat img1 = img.clone();
if (g_step == 1 || g_step == 2) // 鼠标左键点击或按住左键移动时画出矩形
{
rectangle(img1, g_rect, Scalar(0, 255, 0), 2);
}
if (g_step == 2) // 鼠标按住左键移动(矩形已画出)时
{
if (g_max_line != Vec4d(0, 0, 0, 0))
{
// 画出识别到的最长的线段
line(g_image_rect, Point(g_max_line[0], g_max_line[1]), Point(g_max_line[2], g_max_line[3]), Scalar(0, 0, 255), 2);
}
// img1(g_rect) = g_image_rect.clone(); // 错误:clone会img1(g_rect)重新分配内存,修改img1(g_rect)不会改变img
g_image_rect.copyTo(img1(g_rect));
}
if (g_step == 3) // 奇数次按下'1'键时
{
cv::Mat img2 = cv::getRotationMatrix2D(cv::Point2f(img.cols * 0.5f, img.rows * 0.5f), g_angle, 1); // 旋转中心,旋转角度,缩放比例
warpAffine(img1, img1, img2, img1.size()); // 旋转图片
}
imshow(name, img1);
}
// 滑动条的回调函数
void trackbar_callback(int pos, void *)
{
if (g_step == 2) // 鼠标已画出矩形后,才会去识别矩形中最长的线段
{
g_max_line = Vec4d(0, 0, 0, 0);
Mat img;
Canny(g_image_gray, img, g_Canny_th, 3 * g_Canny_th); // 边缘检测
//直线检测,参数:rho,theta,阈值,线段最小长度,共线线段之间的最小间隔
vector<Vec4d> lines;
HoughLinesP(img, lines, 1, CV_PI / 180, g_HoughLinesP_th, g_image_gray.cols * g_HoughLinesP_ml / 100, g_image_gray.cols * g_HoughLinesP_mg / 100);
//获取最长线段的角度
double max_lenght = 0; //线段最大长度
int n = 2;
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i l = lines[i];
if (l[0] > n && l[0] < img.cols - n && l[1] > n && l[1] < img.rows - n) //去除图片边框上的线段
{
int length = g_max_line.rows * g_max_line.rows + g_max_line.cols * g_max_line.cols;
if (max_lenght < length) // 寻找最长线段
{
max_lenght = length;
g_max_line = l;
}
}
}
//计算倾斜角度
if (g_max_line[0] != g_max_line[2])
g_angle = atan((g_max_line[3] - g_max_line[1]) / (g_max_line[2] - g_max_line[0])) * 180 / CV_PI;
else
g_angle = 90;
cvtColor(img, g_image_rect, COLOR_GRAY2BGR); // 灰度图转彩图,方便画彩线
}
}
// 鼠标事件的回调函数
void mouse_callback(int event, int x, int y, int flags, void *param)
{
switch (event)
{
case EVENT_LBUTTONDOWN: // 鼠标左键点击
if (g_step == 0)
{
g_step = 1;
g_rect.x = x;
g_rect.y = y;
}
break;
case EVENT_MOUSEMOVE: // 鼠标移动
if (g_step == 1)
{
g_rect.width = x - g_rect.x;
g_rect.height = y - g_rect.y;
}
break;
case EVENT_LBUTTONUP: // 鼠标左键释放
g_step = 2;
g_image_rect = g_image_original(g_rect).clone();
cvtColor(g_image_rect, g_image_gray, COLOR_BGR2GRAY); // 灰度图
break;
case EVENT_RBUTTONDOWN: // 鼠标右键点击,清除内容
g_step = 0;
g_rect = Rect(0, 0, 0, 0);
g_max_line = Vec4d(0, 0, 0, 0);
break;
default:
break;
}
}
int main()
{
const string name = "image";
namedWindow(name, WINDOW_AUTOSIZE);
g_image_original = imread("E:/VSCode/git/my_program/image/1.PNG");
if (g_image_original.empty())
{
cout << "can not open image!" << endl;
return -1;
}
setMouseCallback(name, mouse_callback); // 鼠标
createTrackbar("threshold1", name, &g_Canny_th, 255, trackbar_callback); // 进度条
createTrackbar("threshold2", name, &g_HoughLinesP_th, 255, trackbar_callback);
createTrackbar("min_length", name, &g_HoughLinesP_ml, 100, trackbar_callback);
createTrackbar(" max_gap", name, &g_HoughLinesP_mg, 100, trackbar_callback);
while (true)
{
trackbar_callback(0, 0); // 进度条函数
draw(name, g_image_original); // 画图
char c = waitKey(10);
if (c == '1') // 按'1'第奇数次,旋转图片;按'1'第偶数次,还原图片
{
if (g_step == 2) // 旋转图片的命令标志
g_step = 3;
else if (g_step == 3) // 还原图片的命令标志
g_step = 2;
}
else if (c > 0 && c != '1') // 退出循环
break;
}
destroyAllWindows();
return 0;
}
滑动条参数:
// 边缘检测
Canny(g_image_gray, img, g_Canny_th, 3 * g_Canny_th);
// 直线检测
HoughLinesP(img, lines, 1, CV_PI / 180, g_HoughLinesP_th, g_image_gray.cols * g_HoughLinesP_ml / 100, g_image_gray.cols * g_HoughLinesP_mg / 100);
参数 | 说明 |
---|---|
threshold1 | 对应g_Canny_th,是检测物体边缘的阈值。 |
threshold2 | 对应g_HoughLinesP_th,是提取直线的阈值。 |
min_length | 对应g_HoughLinesP_ml,由于鼠标画出的矩形,其宽度不固定,故使用百分比。只识别长度大于g_image_gray.cols * g_HoughLinesP_ml / 100的直线。 |
max_gap | 对应g_HoughLinesP_mg,也是使用百分比。若两条小线段在同一条直线上,且其间隔小于g_image_gray.cols * g_HoughLinesP_mg / 100,则将其连接成一条直线,否则将其看作两条直线。 |
操作方法:
运行程序,在空白图片上点击鼠标左键并按住移动,由此画出矩形。设置窗口中的滑动条参数使矩形中出现最长的红线,由此自动计算出倾斜角度。在键盘按下1
键可旋转图片使其水平,再按1
键一次,可使其复原。点击鼠标右键能够擦除已画好的矩形,由此可以重新作画。按键盘除1
以外的键可自动退出程序。
运行结果: