学习OpenCV3:识别图片倾斜角度并自动旋转


一、背景

  现有如下图片,希望能用鼠标画出矩形,在矩形中计算出图片的倾斜角度,并由此自动旋转使图片水平。

二、实现

#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以外的键可自动退出程序。

运行结果

猜你喜欢

转载自blog.csdn.net/qq_34801642/article/details/106844514