opencv-7-鼠标绘制自定义图形

opencvc++qt

开始之前

昨天写了具体的基本的图形绘制, 然后我们使用相应的函数接口进行调用, 便能够在图像上绘制出来相应的图形, 我们以图像绘制为例, 最终都会调用了 Line 函数, 最后都是基于一个 自定义迭代器 LineIterator的形成的路径, 通过设定起始点和目标点, 相当于计算出来的斜率值, 每次累加的之后判断下一个点的位置, 通过 重载累加器的 ++

opencv鼠标python grabcut opencv鼠标画线_自定义

Line 函数调用图

这里不再深入探讨, 相应的可以去研究具体的实现, 很漂亮的源码, 写的很好
我们这个章节 看一个比较高级点的操作, 操作起来比较复杂, 先不去深究其中的原理, 我们先跑起来, 然后再研究

正文

主要是处理鼠标的操作就好, 然后使用绘制图像就好, 可以参考博文《OpenCV:鼠标操作(绘制直线、矩形、圆)简单示例》 和博文OpenCV鼠标画图例程,鼠标绘制矩形,

鼠标绘制图像

我们先梳理一下逻辑, 我们使用两张图片作为轮换, 原始图和临时图, 每次绘制在临时图上, 然后完成之后存入原始图上

  • 初始原始图像, 绘图标志 flg false
  • 鼠标按下, 确定起始点 start_p 标志开始绘图
  • 鼠标移动, 移动目标点 end_p, 绘制到临时图像上
  • 鼠标抬起, 确定最后的点, 然后将图替换原始图, 同时 绘图标志结束flg = false
  • 循环绘制图像, 绘图中的时候 显示临时图像, 绘图之后 显示原始图像,

逻辑理清除之后,我们就想要处理一下细节部分了, 我们在之前的博文中介绍了显示图像的方式, imshow即可显示, 然后我们

发现问题在于程序的鼠标是件监听了, 这里其实比较深入了, 但是呢, 我们不深入去研究, 只去看实现的过程吧,

cv::setMouseCallback(windows_name, on_MouseHandle, 0);, 我们第一个 windows_name 是我们打开的窗口名称, 用于显示图像, 也能通过名称进行句柄操作, 第二个参数就是我们的鼠标处理函数了, on_MouseHandle(int event, int x, int y, int flags, void *param), 这里的函数名称可以自定义, 但是参数类型与个数必须一致, 然后我们就可以通过预定义的事件宏进行确定当前鼠标操作的动作了, 这里 比如cv::EVENT_MOUSEMOVE 宏就是 鼠标移动事件, cv::EVENT_LBUTTONDOWN,和cv::EVENT_LBUTTONUP

编码实现

我们在逻辑理清除 之后, 写起来就很简单了, 可以的大傲下面的代码, 注释也比较清除, 能够一看就懂

#include "mainwindow.h"
#include <QApplication>
// 引入 opencv 函数头文件
#include <opencv2/opencv.hpp>

// 记录鼠标位置点, 以及 正在绘图标志位 flg 
cv::Point start_p(-1, -1), end_p(-1, -1);
bool flg_drawing = false;   

// 使用原始图像与临时图像 存储
cv::Mat src_img, temp_img;
 
//鼠标回调函数 // 记录窗口的x y 位置 
void on_MouseHandle(int event, int x, int y, int flags, void *param)
{
    switch (event)
    {
    case cv::EVENT_LBUTTONDOWN:
    {
        start_p = cv::Point(x, y);      // 确定起始点
        temp_img = src_img.clone();     // 复制原始图, 进行绘图操作
        flg_drawing = true;
    }break;
    case cv::EVENT_MOUSEMOVE:
    {
        if (flg_drawing)
            end_p = cv::Point(x, y);        // 如果在绘制, 则更新移动后的目标点
    }break;
    case cv::EVENT_LBUTTONUP:
    {
        end_p = cv::Point(x, y);        // 确定最终点 
        src_img = temp_img.clone();     // 将图像更新成为原始图 存储下来
        flg_drawing = false;
    }break;
    }
}

// 返回两点之间的距离 直线距离 平方和的开方值
float distance(const cv::Point &p1, const cv::Point &p2)
{
    return cv::sqrt((p1.x - p2.x)*(p1.x - p2.x) + (p1.y - p2.y)*(p1.y - p2.y));
}

int main(int argc, char *argv[])
{
    //QApplication a(argc, argv);
    //MainWindow w;
    //w.show();
    // 设置 要显示的图像路径
    std::string img_lena = "./TestImages/lena.png";
    src_img = cv::imread(img_lena);

    std::string windows_name = "show";
    cv::namedWindow(windows_name,cv::WINDOW_AUTOSIZE);

    // 设置窗口 鼠标操作 监听 函数为 on_MouseHandle
    cv::setMouseCallback(windows_name, on_MouseHandle, 0);

    while (true)
    {
        // 根据当前点 绘制
        if (flg_drawing)
        {
            temp_img = src_img.clone();

            cv::line(temp_img, start_p, end_p, cv::Scalar(0, 255, 0));
            cv::rectangle(temp_img, cv::Rect(start_p, end_p), cv::Scalar(255, 0, 0));
            cv::circle(temp_img, start_p, distance(start_p,end_p), cv::Scalar(0, 0, 255));
            cv::imshow(windows_name, temp_img);
        }
        else
        {
            cv::imshow(windows_name, src_img);
        }
        // 设置 按 esc 退出循环
        if (cv::waitKey(30) == 27)
            break;
    }
    return 0;
    // return a.exec();
}

运行结果

然后就到了我们结果的时候了, 运行之后, 鼠标左键点击开始, 拖动实时绘制, 抬起结束绘制, 然后 我们绘制了 直线, 矩形, 和圆, 同时进行了绘制,

opencv鼠标python grabcut opencv鼠标画线_自定义_02

鼠标绘制图像

ps: 这里录制 gif 使用的是 ScreenToGif 特别好用

我们实现的比较简单, 可以去看我提到的两篇博文, 以及这一篇鼠标作为画笔 翻译的文档,

他们实现的比较复杂, 不仅实现了绘制, 还能够选择绘制什么, 也提供了 随机颜色的方法, cv::RNG

可以参考OpenCV中随机颜色, 随机颜色的解释, 听简单的, 初始化随机, 自动生成随机颜色就行了,

// 随机颜色绘制
cv::line(temp_img, start_p, end_p, cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)));
cv::rectangle(temp_img, cv::Rect(start_p, end_p), cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)));
cv::circle(temp_img, start_p, distance(start_p,end_p), cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)));

opencv鼠标python grabcut opencv鼠标画线_自定义_03

自定义颜色绘制

其他