在之前的创作中心-CSDN滚动条调整图片亮度-CSDN博客创作中心-CSDN中,我们已经了解了滚动条实现亮度以及对比度调节,为了实现对图像中感兴趣区域(ROI, Region of Interest)的交互式选取,本文利用 OpenCV 提供的鼠标事件回调机制,设计并实现了一套基于鼠标操作的图像区域标注功能。用户通过点击鼠标左键确定矩形区域的起点,并在拖动过程中实时显示选框,释放鼠标左键后自动绘制最终的矩形框并截取所选区域。

首先,我们先了解所有的鼠标事件:

事件常量描述(鼠标事件)
EVENT_MOUSEMOVE鼠标移动
EVENT_LBUTTONDOWN鼠标左键按下
EVENT_LBUTTONUP鼠标左键释放
EVENT_LBUTTONDBLCLK鼠标左键双击
EVENT_RBUTTONDOWN鼠标右键按下
EVENT_RBUTTONUP鼠标右键释放
EVENT_RBUTTONDBLCLK鼠标右键双击
EVENT_MBUTTONDOWN鼠标中键按下
EVENT_MBUTTONUP鼠标中键释放
EVENT_MBUTTONDBLCLK鼠标中键双击
EVENT_MOUSEWHEEL鼠标滚轮垂直滚动(上滚为正,下滚为负)
EVENT_MOUSEHWHEEL鼠标滚轮水平滚动(右滚为正,左滚为负)

简单了解之后,需要深度理解鼠标响应函数:cv::setMouseCallback() 

void cv::setMouseCallback(const String& winname, MouseCallback onMouse, void* userdata = 0);
参数类型说明
winnameconst String&要监听鼠标事件的窗口名称,必须是通过 cv::namedWindow() 创建的窗口
onMouseMouseCallback用户定义的鼠标事件回调函数指针
userdatavoid*(可选)可选的用户数据指针,可传递额外参数,如图像指针或上下文结构体

 有了之前的基础后,对于回调函数的定义就较为容易了:

void onMouse(int event, int x, int y, int flags, void* userdata);
参数说明
event当前触发的鼠标事件(如 EVENT_LBUTTONDOWN
x, y鼠标当前在图像坐标系中的位置
flags当前的鼠标键/修饰键状态(如 EVENT_FLAG_CTRLKEY
userdata通过 setMouseCallback() 传递的用户指针

在了解相关的函数后,就可以开始实现开头的功能了,首先,我们依旧是在hpp文件定义这个方法函数:

class Demo{
public:void colorspace_Demo(Mat &image);void Mat_creat(Mat &image);void pixel_RW_Demo(Mat &image);void operator_Demo(Mat &image);void Tracking_Demo(Mat &image);void Color_Demo(Mat &image);void bitwise_Demo(Mat &image);void channel_Demo(Mat &image);void inrange_Demo(Mat &image);void pixel_statistics_Demo(Mat &image);void Shapes_Demo(Mat &image);void polygon_drawing_Demo();void random_Demo();void mouse_Demo(Mat &image);
};

 紧接着回到Demo.cpp定义该函数:

void Demo::mouse_Demo(Mat &image)
{namedWindow("mouse_draw",WINDOW_AUTOSIZE);setMouseCallback("mouse_draw",draw,(void*)(&image));imshow("mouse_draw",image);
}

 首先生成一个窗口,用于鼠标操作显示,draw是回调换函数;接下来定义draw回调函数:接下来,我们一步一步来实现鼠标框选区域的功能:

1.鼠标左键按下;

2.左键按下的同时鼠标移动;

3.左键松去,画出所框区域;

那么针对上述三步,一共有三个鼠标事件:左键按下,鼠标移动,左键松掉,所框区域可以是绘制矩形;思路清晰了;

绘制矩形需要,起始点坐标,结束坐标,长宽,这些就需要程序来获得,我们上述的回调函数中,会获取当前鼠标事件发生时的坐标xy,那起始点以及结束点坐标就有了,对于长宽,也只需要结束点x-起始点x;

首先我们定义两个点,起始点以及结束点:

Point  sp(-1,-1);
Point  ep(-1,-1); 

回调函数定义:

static void draw(int event,int x,int y,int flags, void *userdata) 
{Mat image = *((Mat*)userdata);
}

 Mat image = *((Mat*)userdata);这一步就不过多解释;

 1.鼠标左键按下;我们要做的就是记录下起始点的坐标:

    if (event == EVENT_LBUTTONDOWN){sp.x = x;sp.y = y;cout<<sp<<endl;}

我们先编写鼠标松掉,看能否绘制矩形:

else if (event == EVENT_LBUTTONUP){ep.x = x;ep.y = y;int dx = ep.x - sp.x;int dy = ep.y - sp.y;Rect box(sp.x,sp.y,dx,dy);rectangle(image,box,Scalar(255,0,0),4,LINE_8);imshow("ROI",image(box));imshow("mouse_draw",image);sp.x=-1;sp.y=-1;ep.x=-1;ep.y=-1;}

同样先做的是记录下结束的坐标,这样我们就得到了两个先决条件 :那么长宽就计算出来了:

int dx = ep.x - sp.x;
int dy = ep.y - sp.y;

问题来了,这里的dx,dy是存在负数的情况的,看下面的图片; 

左上角的点为(0,0);如果从左上角拉到右下角就是正数,而从右下角拉到左上角就是负数; 

 我们只需要分情况就行了:

第一种正数:

        if (dx>0&&dy>0){Rect box(sp.x,sp.y,dx,dy);rectangle(image,box,Scalar(255,0,0),4,LINE_8);imshow("ROI",image(box));}

第二种负数: 

        {dx=-dx;dy=-dy;Rect box(ep.x,ep.y,dx,dy);rectangle(image,box,Scalar(255,0,0),4,LINE_8);imshow("ROI",image(box));}

负数我们只需要将dxdy取反得正,那么应该将结束点作为起始点绘制矩形就可以了;这里的imshow("ROI",image(box));用于显示所框选的区域;到这一步我们的程序应该是这样:

Point  sp(-1,-1);
Point  ep(-1,-1); static void draw(int event,int x,int y,int flags, void *userdata) 
{Mat image = *((Mat*)userdata);if (event == EVENT_LBUTTONDOWN){sp.x = x;sp.y = y;cout<<sp<<endl;}else if (event == EVENT_LBUTTONUP){ep.x = x;ep.y = y;int dx = ep.x - sp.x;int dy = ep.y - sp.y;if (dx>0&&dy>0){Rect box(sp.x,sp.y,dx,dy);rectangle(image,box,Scalar(255,0,0),4,LINE_8);imshow("ROI",image(box));}if (dx<0&&dy<0){dx=-dx;dy=-dy;Rect box(ep.x,ep.y,dx,dy);rectangle(image,box,Scalar(255,0,0),4,LINE_8);imshow("ROI",image(box));}imshow("mouse_draw",image);sp.x=-1;sp.y=-1;ep.x=-1;ep.y=-1;}
}

 运行发现,整体大致没什么问题,但是我们在拉动的时候,希望这个矩形能跟着鼠标移动:并且画面也没有更新,绘制的框在绘制下一个依旧显示在上面;

现在,我们还缺少鼠标移动事件的情况,编写之后,再接着纠错,实际上很简单,鼠标移动时,矩形跟着放大或者变小,实际上也是鼠标移动,绘制矩形;只需要复制粘贴,

   else if (event == EVENT_MOUSEMOVE){if(sp.x > 0 && sp.y > 0){ep.x = x;ep.y = y;int dx = ep.x - sp.x;int dy = ep.y - sp.y;if (dx>0&&dy>0){Rect box(sp.x,sp.y,dx,dy);rectangle(image,box,Scalar(255,0,0),4,LINE_8);}if (dx<0&&dy<0){dx=-dx;dy=-dy;Rect box(ep.x,ep.y,dx,dy);rectangle(image,box,Scalar(255,0,0),4,LINE_8);}imshow("mouse_draw", image);  }}

 运行发现好像有1.问题:

 所幸我打开GPT,得知问题所在:鼠标移动绘制,但是我们应该给一个原图刷新不让其绘制很多矩形,解决方案就是在定义一个全局变量temp;在方法函数中拷贝原图;在绘制完矩形后,立刻将这个temp拷贝到绘制矩形的图片上,就可以只绘制第一个矩形,其他都被原图覆盖了;

    else if (event == EVENT_MOUSEMOVE){if(sp.x > 0 && sp.y > 0){ep.x = x;ep.y = y;int dx = ep.x - sp.x;int dy = ep.y - sp.y;if (dx>0&&dy>0){Rect box(sp.x,sp.y,dx,dy);temp.copyTo(image);rectangle(image,box,Scalar(255,0,0),4,LINE_8);}if (dx<0&&dy<0){dx=-dx;dy=-dy;Rect box(ep.x,ep.y,dx,dy);temp.copyTo(image);rectangle(image,box,Scalar(255,0,0),4,LINE_8);}imshow("mouse_draw", image);  // 🟢 实时刷新窗口}}

运行发现成功;

那整体的回调函数定义就是:

static void draw(int event,int x,int y,int flags, void *userdata) 
{Mat image = *((Mat*)userdata);if (event == EVENT_LBUTTONDOWN){sp.x = x;sp.y = y;cout<<sp<<endl;}else if (event == EVENT_MOUSEMOVE){if(sp.x > 0 && sp.y > 0){ep.x = x;ep.y = y;int dx = ep.x - sp.x;int dy = ep.y - sp.y;if (dx>0&&dy>0){Rect box(sp.x,sp.y,dx,dy);temp.copyTo(image);rectangle(image,box,Scalar(255,0,0),4,LINE_8);}if (dx<0&&dy<0){dx=-dx;dy=-dy;Rect box(ep.x,ep.y,dx,dy);temp.copyTo(image);rectangle(image,box,Scalar(255,0,0),4,LINE_8);}imshow("mouse_draw", image);  // 🟢 实时刷新窗口}}else if (event == EVENT_LBUTTONUP){ep.x = x;ep.y = y;int dx = ep.x - sp.x;int dy = ep.y - sp.y;if (dx>0&&dy>0){Rect box(sp.x,sp.y,dx,dy);rectangle(image,box,Scalar(255,0,0),4,LINE_8);imshow("ROI",image(box));}if (dx<0&&dy<0){dx=-dx;dy=-dy;Rect box(ep.x,ep.y,dx,dy);rectangle(image,box,Scalar(255,0,0),4,LINE_8);imshow("ROI",image(box));}imshow("mouse_draw",image);sp.x=-1;sp.y=-1;ep.x=-1;ep.y=-1;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/91816.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/91816.shtml
英文地址,请注明出处:http://en.pswp.cn/web/91816.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

True or False? 基于 BERT 学生数学问题误解检测

True or False? 基于 BERT 学生数学问题误解检测 代码详见&#xff1a;https://github.com/xiaozhou-alt/Student_Math_Misconception 文章目录True or False? 基于 BERT 学生数学问题误解检测一、项目介绍二、文件夹结构三、数据集介绍四、BERT 模型介绍五、项目实现1. 数据…

小程序基于vue+nodejs的私人定做订制订单发布与对应商品出售平台

文章目录项目介绍主要技术与实现手段具体实现截图关于我本系统开发思路研究思路、方法和步骤java类核心代码部分展示系统测试本系统技术可行性分析源码获取详细视频演示或者查看其他版本&#xff1a;文章底部获取博主联系方式&#xff01;项目介绍主要技术与实现手段 uni-app框…

为什么要有动态内存分配?

文章目录1.为什么要有动态内存分配2.malloc和free2.1 malloc2.2 free3.calloc和realloc3.1 calloc3.2 realloc4.常见的动态内存的错误4.1 对NULL指针的解引用操作4.2 对动态开辟空间的越界访问4.3 对⾮动态开辟内存使⽤free释放4.4 使⽤free释放⼀块动态开辟内存的⼀部分4.5 对…

docker hub 拉取镜像失败报Get “https://registry-1.docker.io/v2/“: net/http: request canceled while waiting

自己记录一把&#xff0c;给兄弟们避坑 1.上问题报错代码 [rootlocalhost ~]# docker pull hello-world Using default tag: latestError response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connectio…

Hive数仓部署/分层/ETL脚本基础指南

部署Hive数仓及分层基础指南 部署和构建一个Hive数仓涉及多个步骤&#xff0c;包括设置Hadoop集群、配置Hive、设计数据仓库架构以及实现ETL&#xff08;Extract, Transform, Load&#xff09;过程。下面是一个详细的指南&#xff0c;帮助你完成这些步骤。 1. 设置Hadoop集群 首…

深入 Go 底层原理(六):垃圾回收(GC)

1. 引言Go 语言自带垃圾回收&#xff08;Garbage Collection, GC&#xff09;&#xff0c;让开发者从手动管理内存的繁重任务中解脱出来。Go 的 GC 以其低延迟和并发性而闻名&#xff0c;其目标是在不长时间暂停&#xff08;Stop The World, STW&#xff09;整个程序的情况下完…

专网内网IP攻击防御:从应急响应到架构加固

内网IP攻击防御&#xff1a;从应急响应到架构加固内网IP攻击的隐蔽性远超外网威胁&#xff0c;其本质是信任边界内的权限滥用。应对需遵循"识别-隔离-溯源-加固"四步法则&#xff0c;兼顾应急止损与长效防御。应急处置&#xff1a;30分钟响应窗口1. 流量阻断&#xf…

Git、Gitee、GitHub、GitLab完整讲解:从基础到进阶

第一部分&#xff1a;Git是什么&#xff1f; &#x1f4da;比喻&#xff1a;Git就像是一本"时光日记本" ✅ 每一段代码的改动&#xff0c;Git都会帮你记录下来&#xff0c;像是在写日记。 ✅ 如果出现问题或者想查看之前的版本&#xff0c;Git可以带你"穿越回…

WinForm之CheckBox 控件

CheckBox&#xff08;复选框&#xff09;是 WinForm 中用于实现 “多项选择” 的控件&#xff0c;允许用户从一组选项中选择任意数量的项&#xff08;包括零项、一项或多项&#xff09;&#xff0c;适用于需要同时选择多个选项的场景&#xff08;如爱好、权限设置、功能开关等&…

鲸鱼优化算法(Whale Optimization Algorithm, WOA)是一种受座头鲸捕食行为启发的群体智能优化算法,由Seyedali Mirjalili于2016年提出

鲸鱼优化算法(Whale Optimization Algorithm, WOA)是一种受座头鲸捕食行为启发的群体智能优化算法,由Seyedali Mirjalili于2016年提出。 它通过模拟鲸鱼的狩猎策略(特别是“气泡网捕食”行为)来解决优化问题,广泛应用于函数优化、工程设计、机器学习参数优化等领域。以下…

信息量,惊奇度,熵、KL散度(相对熵),交叉熵、最大似然估计MLE与最小化交叉熵的等价证明、

一&#xff1a; 一些基本概念 1.1 信息量:特定事件所携带的信息多少信息量衡量的是特定事件所携带的信息多少&#xff0c;其数学定义为&#xff1a;其中p(x)是事件x发生的概率。核心思想&#xff1a;越罕见的事件&#xff0c;其携带的信息量越大&#xff1b;越常见的事件&#…

VBA 64位API声明语句第012讲

跟我学VBA&#xff0c;我这里专注VBA, 授人以渔。我98年开始&#xff0c;从源码接触VBA已经20余年了&#xff0c;随着年龄的增长&#xff0c;越来越觉得有必要把这项技能传递给需要这项技术的职场人员。希望职场和数据打交道的朋友&#xff0c;都来学习VBA,利用VBA,起码可以提高…

深入理解Java中String.intern()方法:从原理到并发控制实践

深入理解 Java 中 String.intern () 方法&#xff1a;从原理到并发控制实践 在 Java 开发中&#xff0c;String.intern()方法是一个看似简单却蕴含深意的 API。它在字符串常量池管理、内存优化以及并发控制等场景中有着关键作用。本文将从底层原理出发&#xff0c;结合实际案例…

在Linux中创建LVGL应用

在Linux中创建LVGL应用 简介 上一篇文章介绍了在imx6上开发UI的流程 . 这篇接上文&#xff0c; 介绍具体的开发步骤。 1. 创建项目主目录 mkdir my_lvgl_project cd my_lvgl_project2. 初始化 Git 仓库 (可选但推荐) git init echo "# My Project with Dependencies&…

大模型对比评测:Qwen2.5 VS Gemini 2.0谁更能打?

一、背景与选型关键 在 AI 应用落地的时代&#xff0c;“AI大模型选型对比”成为关键环节。选择合适的模型要综合考量性能、上下文长度、推理能力、中文/编程支持、成本等多维度指标。 本文重点比较 Gemini2.0Flash-Lite &#xff08;Preview&#xff09;、Gemini2.0Flash &a…

转置卷积解释与示例计算

文章目录转置卷积的三种等价实现方法&#xff1a;原理、公式与等价性分析数学定义与核心公式方法一&#xff1a;零填充翻转核卷积&#xff08;数学定义方法&#xff09;原理与公式等价性说明方法二&#xff1a;直接位置映射&#xff08;pytorch框架高效实现&#xff09;原理与公…

关于车位引导及汽车乘梯解决方案的专业性、系统性、可落地性强的综合设计方案与技术实现说明,旨在为现代智慧停车楼提供高效、安全、智能的停车体验。

一、系统概述随着城市土地资源日益紧张&#xff0c;立体停车、自动化停车成为发展趋势。本方案围绕“车位引导系统 汽车乘梯系统”构建智慧停车核心体系&#xff0c;结合地磁/视频/超声波检测、AI识别、语音交互、电梯自动调度等先进技术&#xff0c;实现车辆入场、引导、停泊…

【相机】曝光时间长-->拖影

曝光时间长 → 运动目标在快门开启期间持续移动 → 同一像素记录多个位置的能量 → 图像出现“拖影”&#xff08;运动模糊&#xff09;。&#x1f50d; 具体原因卷帘快门&#xff08;Rolling Shutter&#xff09;效应 RealSense 的 RGB 传感器&#xff08;如 IMX 系列&#xf…

day36 力扣1049.最后一块石头的重量II 力扣494.目标和 力扣474.一和零

最后一块石头的重量II有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x < y。那么粉碎的可能结果如下&#…

Java内存模型(Java Memory Model,JMM)

​​ JMM​​ 是Java虚拟机&#xff08;JVM&#xff09;规范中定义的一组规则和规范&#xff0c;用于描述多线程环境下&#xff0c;Java程序中变量的访问和修改行为&#xff0c;尤其是在并发编程中如何保证内存可见性、原子性和有序性。JMM 是 Java 并发编程的基石&…