学习OpenCV3:warpAffine旋转图片,鼠标操作截图


1、背景

  现有如下一张图片,发现其图案与图片并不平行,故希望先通过滑动条旋转图片角度使图案水平,再将旋转好后的图片截图保存下来。

测试图片:

2、实现

初始版本:实现图片旋转功能。
缺陷:旋转时超出图片大小的部分无法显示。

#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <cmath>

// 全局变量
cv::Mat g_image_original, g_image_result; // 原始图片,处理后的图片

// 滑动条回调函数
void trackbar_callback(int angle, void *)
{
    // 旋转中心,旋转角度,缩放比例
    cv::Mat img = cv::getRotationMatrix2D(cv::Point2f(g_image_original.cols * 0.5f, g_image_original.rows * 0.5f), angle, 1);
    // 旋转图片
    warpAffine(g_image_original, g_image_result, img, g_image_original.size());
}

int main()
{
    const std::string window_name = "image";
    const std::string path = "E:/VSCode/git/my_program/image/5_1.PNG";
    cv::namedWindow(window_name, cv::WINDOW_AUTOSIZE);
    g_image_original = cv::imread(path);
    if (g_image_original.empty())
    {
        std::cout << "can not open image!" << std::endl;
        return -1;
    }
    // 进度条
    int angle = 0; // 图片旋转的角度
    cv::createTrackbar("angle", window_name, &angle, 360, trackbar_callback);
    while (true)
    {
        trackbar_callback(angle, 0); // 进度条回调函数
        cv::imshow(window_name, g_image_result);
        if (cv::waitKey(10) >= 0)
            break;
    }
    cv::destroyAllWindows();
    return 0;
}

旋转前:

旋转后:

  


中间版本:解决图片旋转后无法全部显示的问题。
实现方法:旋转前,放大图片使其大小足以容纳原图旋转。旋转时,将已放大的图片缩小至原大小。
缺陷:无参照物以确保图案平行。

#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <cmath>

// 全局变量
cv::Mat g_image_original, g_image_result; // 原始图片,处理后的图片

// 滑动条回调函数
void trackbar_callback(int angle, void *enlarge)
{
    double m = *(double *)enlarge;
    // 旋转中心,旋转角度,缩放比例
    cv::Mat img = cv::getRotationMatrix2D(cv::Point2f(g_image_original.cols * 0.5f, g_image_original.rows * 0.5f), angle, 1 / m); // 缩小图片
    // 旋转图片
    warpAffine(g_image_original, g_image_result, img, g_image_original.size());
}

int main()
{
    const std::string window_name = "image";
    const std::string path = "E:/VSCode/git/my_program/image/5_1.PNG";
    cv::namedWindow(window_name, cv::WINDOW_AUTOSIZE);
    g_image_original = cv::imread(path);
    if (g_image_original.empty())
    {
        std::cout << "can not open image!" << std::endl;
        return -1;
    }
    // 放大图片
    double m1 = std::sqrt(std::pow(g_image_original.cols, 2) + std::pow(g_image_original.rows, 2)); //计算对角线长度
    double m2 = g_image_original.cols > g_image_original.rows ? g_image_original.rows : g_image_original.cols;
    double m = m1 / m2; // 放大比例
    cv::resize(g_image_original, g_image_original, cv::Size(g_image_original.cols * m, g_image_original.rows * m));
    // 进度条
    int angle = 0; // 图片旋转的角度
    cv::createTrackbar("angle", window_name, &angle, 360, trackbar_callback, &m);
    while (true)
    {
        trackbar_callback(angle, &m); // 进度条回调函数
        cv::imshow(window_name, g_image_result);
        if (cv::waitKey(10) >= 0)
            break;
    }
    cv::destroyAllWindows();
    return 0;
}

旋转前:

旋转后:

  


最终版本:通过鼠标调整4条直线使其分布在图案四周,方便确定图案已平行,同时也可用于截取图案。
操作方法:首先按下鼠标左键选择任意一条绿线,然后一直按住左键移动鼠标使绿线接近图案。同样方法移动其它3条绿线,使其包围住图案。通过滑动条旋转角度使图案与绿线平行,按s键保存4条绿线包围的矩形图片。

#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <cmath>

// 全局变量
cv::Mat g_image_original, g_image_result; // 原始图片,处理后的图片
cv::Rect g_image_xywh;                    // 可通过鼠标移动由4条直线组成的矩形

// 画4条直线
void draw_lines(cv::Mat img, cv::Rect rect)
{
    cv::Scalar color(0, 255, 0); // 颜色
    int thickness = 2;           // 厚度
    // 按顺时针画4条线
    cv::line(img, cv::Point(0, rect.y), cv::Point(img.cols, rect.y), color, thickness);                             // 上边
    cv::line(img, cv::Point(rect.x + rect.width, 0), cv::Point(rect.x + rect.width, img.rows), color, thickness);   // 右边
    cv::line(img, cv::Point(0, rect.y + rect.height), cv::Point(img.cols, rect.y + rect.height), color, thickness); // 下边
    cv::line(img, cv::Point(rect.x, 0), cv::Point(rect.x, img.rows), color, thickness);                             // 左边
}

// 滑动条的回调函数,用于旋转图片
void trackbar_callback(int angle, void *enlarge)
{
    double m = *(double *)enlarge;
    // 旋转中心,旋转角度,缩放比例
    cv::Mat img = cv::getRotationMatrix2D(cv::Point2f(g_image_original.cols * 0.5f, g_image_original.rows * 0.5f), angle, 1 / m); // 缩小图片
    // 旋转图片
    warpAffine(g_image_original, g_image_result, img, g_image_original.size());
    // 画4条直线
    draw_lines(g_image_result, g_image_xywh);
}

// 确定鼠标所点击在那一条线上
int mouse_location(cv::Rect rect, cv::Point p)
{
    static int n = 10;
    if (p.y > rect.y - n && p.y < rect.y + n) //上边
        return 1;
    else if (p.x > rect.x + rect.width - n && p.x < rect.x + rect.width + n) //右边
        return 2;
    else if (p.y > rect.y + rect.height - n && p.y < rect.y + rect.height + n) //下边
        return 3;
    else if (p.x > rect.x - n && p.x < rect.x + n) //左边
        return 4;
    else
        return -1;
}

// 矫正坐标
void correct(int &m, int n1, int n2)
{
    if (m < n1)
        m = n1;
    else if (m > n2)
        m = n2;
}

// 鼠标事件的回调函数,用于移动4条直线以判断图片是否平行,按's'键截取图片并保存
void mouse_callback(int event, int x, int y, int flags, void *param)
{
    static cv::Rect rect(0, 0, 0, 0);    // 暂存g_image_xywh
    static cv::Point p1(0, 0), p2(0, 0); // 鼠标左键点击的点、鼠标按住左键移动的点
    static int location = -1;            // 鼠标左键点击落在那条直线上
    cv::Size size(0, 0);                 // 鼠标移动距离
    switch (event)
    {
    case cv::EVENT_LBUTTONDOWN: // 鼠标左键点击
        rect = g_image_xywh;
        p1 = cv::Point(x, y);
        location = mouse_location(g_image_xywh, p1);
        break;
    case cv::EVENT_MOUSEMOVE: // 鼠标移动
        p2 = cv::Point(x, y);
        size = cv::Size(p2.x - p1.x, p2.y - p1.y);
        if (location == 1) //上边
        {
            g_image_xywh.y = rect.y + size.height;
            correct(g_image_xywh.y, 0, g_image_result.rows); // 矫正坐标
            g_image_xywh.height = rect.y + rect.height - g_image_xywh.y;
        }
        else if (location == 2) //右边
        {
            g_image_xywh.width = rect.width + size.width;
            correct(g_image_xywh.width, g_image_xywh.width < -rect.x, g_image_result.cols - rect.x); // 矫正坐标
        }
        else if (location == 3) //下边
        {
            g_image_xywh.height = rect.height + size.height;
            correct(g_image_xywh.height, -rect.y, g_image_result.rows - rect.y); // 矫正坐标
        }
        else if (location == 4) //左边
        {
            g_image_xywh.x = rect.x + size.width;
            correct(g_image_xywh.x, 0, g_image_result.cols); // 矫正坐标
            g_image_xywh.width = rect.x + rect.width - g_image_xywh.x;
        }
        draw_lines(g_image_result, g_image_xywh);
        break;
    case cv::EVENT_LBUTTONUP: // 鼠标左键释放
        rect = cv::Rect(0, 0, 0, 0);
        p1 = cv::Point(0, 0);
        p2 = cv::Point(0, 0);
        location = -1;
        break;
    default:
        break;
    }
}

int main()
{
    const std::string window_name = "image";
    const std::string path = "E:/VSCode/git/my_program/image/5_1.PNG";
    int angle = 0; //图片旋转的角度
    g_image_original = cv::imread(path);
    if (g_image_original.empty())
    {
        std::cout << "can not open image!" << std::endl;
        return -1;
    }
    cv::namedWindow(window_name, cv::WINDOW_AUTOSIZE);

    // 初始化g_image_xywh
    g_image_xywh.width = g_image_original.cols;
    g_image_xywh.height = g_image_original.rows;

    //放大图片,以确保旋转图片时图片保持放大前的尺寸
    double m1 = std::sqrt(std::pow(g_image_original.cols, 2) + std::pow(g_image_original.rows, 2)); //计算对角线长度
    double m2 = g_image_original.cols > g_image_original.rows ? g_image_original.rows : g_image_original.cols;
    double m = m1 / m2; // 放大比例
    cv::resize(g_image_original, g_image_original, cv::Size(g_image_original.cols * m, g_image_original.rows * m));

    // 初始化g_image_xywh
    g_image_xywh.x = (g_image_original.cols - g_image_xywh.width) / 2;
    g_image_xywh.y = (g_image_original.rows - g_image_xywh.height) / 2;

    //鼠标回调函数
    cv::setMouseCallback(window_name, mouse_callback);
    // 进度条回调函数
    cv::createTrackbar("   angle:", window_name, &angle, 180, trackbar_callback, &m);
    while (true)
    {
        trackbar_callback(angle, &m);
        cv::imshow(window_name, g_image_result);
        char c = cv::waitKey(10);
        if (c == 's') // 按's'保存图片
        {
            int x1 = g_image_xywh.width > 0 ? g_image_xywh.x : g_image_xywh.x + g_image_xywh.width;
            int y1 = g_image_xywh.height > 0 ? g_image_xywh.y : g_image_xywh.y + g_image_xywh.height;
            int thickness = 2; // 由于在图片上画线,截取时需减去线的厚度
            cv::Rect rect(x1 + thickness, y1 + thickness, abs(g_image_xywh.width) - 2 * thickness, abs(g_image_xywh.height) - 2 * thickness);
            cv::imwrite("1.png", g_image_result(rect));
        }
        else if (c > 0 && c != 's') // 按除's'外的键以退出循环
            break;
    }
    cv::destroyAllWindows();
    return 0;
}

旋转前:

旋转后:

截取的图片:

猜你喜欢

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