opencv-rust 系列3: Create_mask
前言: 这里只是opencv-rust自带示例的中文注解. 略微增加了一些代码也是我在调试时用到的. 调试方法可参见前文.
一. 这个程序还是有点难度的, 关键点在于:
- 创建了遮罩. 直接调用一个函数, 还是很简单的.
- 窗口事件处理. 注册窗口回调函数, 用以处理鼠标事件
- 进程同步和互斥锁. 为什么会是多进程? 一个是系统进程产生鼠标事件和数据, 另一个是本程序进程处理鼠标事件产生的数据. 这里的数据是同一个变量, 所以需要进程同步与互斥
- 状态机方式处理程序的流转.
二. 程序中的技巧:
- 常用的多变量集体定义和集体赋值方式:
let [src, mut next_frame, mut mask, mut final_img]: [Mat; 4]; //集体定义
let (mouse_event_data, should_handle_mouse_event) = (Arc::new(Mutex::new(mouse_event_data)), Arc::new(AtomicBool::new(false))); //集体赋值
- 原程序中:
highgui::named_window(SOURCE_WINDOW, highgui::WINDOW_AUTOSIZE)?;
窗口标志必须加上|highgui::WINDOW_GUI_NORMAL
, 不然鼠标右键会引起混乱.
三. 重点程序逻辑:
- 注册窗口回调函数
highgui::set_mouse_callback(SOURCE_WINDOW, Some(Box::new(mouse_event_dispatcher)))
后, 如果在SOURCE_WINDOW
窗口内发生鼠标事件, 操作系统会将此事件分发给mouse_event_dispatcher
函数处理. 调用这个函数会需要四个参数,即(event: i32, x: i32, y: i32, flags: i32)
, 这是由操作系统根据实际情况提供. mouse_event_dispatcher
函数几乎原封不动地传给&mouse_event_data
, 并将should_handle_mouse_event
标志置true
, 等待程序来处理.- 主程序就是一个大循环
loop{}
. 里面首先就是查询上面的should_handle_mouse_event
标志是否为true
, 如果不是就进入下一循环, 如果为true
就去获取*mouse_event_data
的值, 并赋给(mouse_event, x, y, _)
. - 接下来就是根据当前状态和鼠标事件更新绘图状态
drawing_state = state_transform(drawing_state, mouse_event);
,然后进入状态机的状态流转.
下面附上作了中文注释的原代码:
//! # 本程序演示如何创建遮罩和如何处理鼠标事件
//! Reference: [opencv/samples/cpp/create_mask.cpp](https://github.com/opencv/opencv/blob/4.9.0/samples/cpp/create_mask.cpp)use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::{env, process};use opencv::core::{bitwise_and, find_file, CommandLineParser, Point, Scalar, Vec3b};
use opencv::highgui::imshow;
use opencv::imgcodecs::{imread, IMREAD_COLOR};
use opencv::prelude::*;
use opencv::{highgui, imgproc, not_opencv_branch_4, opencv_branch_4, Result};// 根据OpenCV版本选择正确的宏
opencv_branch_4! {use opencv::imgproc::LINE_8;
}
not_opencv_branch_4! {use opencv::core::LINE_8;
}const SOURCE_WINDOW: &str = "Source image";// 源图像窗口名称常量
// 绘图状态枚举
#[derive(Debug)]
enum DrawingState {Init, // 初始状态DrawingMarkerPoint, // 绘制标记点DrawingMarkerPointFinished, // 标记点绘制完成DrawingMask, // 绘制遮罩DrawingMaskFinished, // 遮罩绘制完成Resetting, // 重置
}fn main() -> Result<()> {// 获取命令行参数let args: Vec<String> = env::args().collect();let argv = args.iter().map(|s| s.as_str()).collect::<Vec<&str>>();// 创建命令行解析器let mut parser = CommandLineParser::new(&argv, "{@input | lena.jpg | input image}")?;//设置程序的 about 信息parser.about("This program demonstrates using mouse events\n")?;//打印程序的 about 信息parser.print_message()?;println!("\n\tleft mouse button - set a point to create mask shape\n\\tright mouse button - create mask from points\n\\tmiddle mouse button - reset");//获取输入图像路径let input_image = argv.into_iter().nth(2).unwrap_or("./examples/data/1.jpg"); //lena.jpg"); //校验文件是否存在let input_image_path = find_file(input_image, true, false).map(|path| {println!("find input_image {} in : {}", input_image, path);path}).unwrap_or_else(|_| panic!("Cannot find input_image: {}", input_image));// 初始化图像变量let [src, mut next_frame, mut mask, mut final_img]: [Mat; 4];//读取图片文件src = imread(&input_image_path, IMREAD_COLOR)?; if src.empty() {eprintln!("Error opening image: {}", input_image);process::exit(-1);}// 创建源图像窗口// 必须加上 highgui::WINDOW_GUI_NORMAL 去除右键菜单,否则鼠标右键会引起混乱highgui::named_window(SOURCE_WINDOW, highgui::WINDOW_AUTOSIZE|highgui::WINDOW_GUI_NORMAL)?;// 初始化鼠标事件数据let mouse_event_data = (highgui::MouseEventTypes::EVENT_MOUSEWHEEL, 0, 0, 0);// 创建用于同步的原子布尔和互斥锁//移动鼠标和本程序都会处理鼠标事件,所以需要互斥锁let (mouse_event_data, should_handle_mouse_event) = (Arc::new(Mutex::new(mouse_event_data)), Arc::new(AtomicBool::new(false)));// 创建鼠标事件分发器. 定义了一个闭包函数mouse_event_dispatcher, 接受四个参数:event: i32, x: i32, y: i32, flags: i32let mouse_event_dispatcher = {let mouse_data = Arc::clone(&mouse_event_data);let should_handle_mouse_event = Arc::clone(&should_handle_mouse_event);move |event: i32, x: i32, y: i32, flags: i32| { //注意:这里的四个参数其实是mouse_event_dispatcher需要调用的参数// can intercept specific mouse events here to don't update the mouse_data//尝试将 event 转换成 highgui::MouseEventTypes 枚举if let Ok(mouse_event) = highgui::MouseEventTypes::try_from(event) {//尝试获取 mouse_data 的互斥锁if let Ok(mut mouse_data) = mouse_data.lock() {*mouse_data = (mouse_event, x, y, flags);}}should_handle_mouse_event.store(true, Ordering::Relaxed);}};// 设置鼠标回调函数. 将鼠标事件绑定到窗口highgui::set_mouse_callback(SOURCE_WINDOW, Some(Box::new(mouse_event_dispatcher))).expect("Cannot set mouse callback");//显示最初图片imshow(SOURCE_WINDOW, &src)?;//初始化标记点和绘图状态 let (mut marker_points, mut drawing_state) = (Vec::<Point>::new(), DrawingState::Init);// 创建一个与源图像大小相同的黑色图像next_frame = Mat::zeros_size(src.size()?, Vec3b::opencv_type())?.to_mat()?;// 主事件循环loop {// Press Esc to exit 如果按下Esc键,则退出循环if highgui::wait_key(10)? == 27 {break Ok(());}// 获取并处理鼠标事件//let (mouse_event, x, y, _) = { //这一段只是根据取到的mouse_event_data值赋给 (mouse_event, x, y, _)if !should_handle_mouse_event.load(Ordering::Relaxed) {continue; // 如果没有鼠标事件需要处理,则继续下一次循环} else {should_handle_mouse_event.store(false, Ordering::Relaxed);// 重置鼠标事件处理标志//下面这句将新变量mouse_event_data绑定到锁上, 并通过解引用获取到值, 并将值赋给了最初的(mouse_event, x, y, _)if let Ok(mouse_event_data) = mouse_event_data.lock() {*mouse_event_data // 获取鼠标事件数据 } else {continue; // 如果无法获取鼠标事件数据,则继续下一次循环}}};// 根据当前状态和鼠标事件更新绘图状态drawing_state = state_transform(drawing_state, mouse_event);// 根据绘图状态执行不同的操作match drawing_state {DrawingState::Init | DrawingState::DrawingMarkerPointFinished => { /* do nothing */ }DrawingState::DrawingMarkerPoint => {// 如果标记点列表为空,则复制源图像到下一帧if marker_points.is_empty() {next_frame = src.clone();}// 创建一个新的标记点并添加到列表中let point = Point::new(x, y);imgproc::circle(&mut next_frame, point, 2, Scalar::new(0., 0., 255., 0.), -1, LINE_8, 0)?;marker_points.push(point);// 如果标记点列表中有两个以上的点,则绘制线段if marker_points.len() > 1 {imgproc::line(&mut next_frame,marker_points[marker_points.len() - 2],point,Scalar::new(0., 0., 255., 0.),2,LINE_8,0,)?;}// 显示下一帧图像imshow(SOURCE_WINDOW, &next_frame)?;}DrawingState::DrawingMask => {// 如果标记点列表不为空,则复制源图像到下一帧并绘制多边形if !marker_points.is_empty() {next_frame = src.clone();imgproc::polylines(&mut next_frame,&Mat::from_slice(marker_points.as_slice())?,true,Scalar::new(0., 0., 0., 0.),2,LINE_8,0,)?;// 显示下一帧图像imshow(SOURCE_WINDOW, &next_frame)?;}}DrawingState::DrawingMaskFinished => {// 如果标记点列表不为空,则创建遮罩和最终图像if !marker_points.is_empty() {final_img = Mat::zeros_size(src.size()?, Vec3b::opencv_type())?.to_mat()?;mask = Mat::zeros_size(src.size()?, u8::opencv_type())?.to_mat()?;imgproc::fill_poly_def(&mut mask, &Mat::from_slice(marker_points.as_slice())?, Scalar::all(255.))?;bitwise_and(&src, &src, &mut final_img, &mask)?;// 使用遮罩对源图像进行位运算imshow("Mask", &mask)?; // 显示遮罩图像imshow("Result", &final_img)?; // 显示最终图像imshow(SOURCE_WINDOW, &next_frame)?; // 显示下一帧图像}}DrawingState::Resetting => {// 如果标记点列表不为空,则清空列表并复制源图像到下一帧if !marker_points.is_empty() {marker_points.clear();next_frame = src.clone();// 显示下一帧图像imshow(SOURCE_WINDOW, &next_frame)?;}}}}
}fn state_transform(drawing_state: DrawingState, mouse_event: highgui::MouseEventTypes) -> DrawingState {use opencv::highgui::MouseEventTypes::*;use self::DrawingState::*;// 使用 match 表达式来根据当前绘图状态和鼠标事件类型进行模式匹配match (&drawing_state, mouse_event) {// 如果当前状态是 Init 并且发生的事件是鼠标左键按下,则转换为 DrawingMarkerPoint 状态(Init, EVENT_LBUTTONDOWN) => DrawingMarkerPoint,// 如果当前状态是 DrawingMarkerPoint 并且发生的事件是鼠标左键释放,则转换为 DrawingMarkerPointFinished 状态(DrawingMarkerPoint, EVENT_LBUTTONUP) => DrawingMarkerPointFinished,// 如果当前状态是 DrawingMarkerPointFinished 并且发生的事件是鼠标左键按下,则转换回 DrawingMarkerPoint 状态(DrawingMarkerPointFinished, EVENT_LBUTTONDOWN) => DrawingMarkerPoint,// 如果当前状态是 DrawingMarkerPointFinished 并且发生的事件是鼠标右键按下,则转换为 DrawingMask 状态(DrawingMarkerPointFinished, EVENT_RBUTTONDOWN) => DrawingMask,// 如果当前状态是 DrawingMask 并且发生的事件是鼠标右键释放,则转换为 DrawingMaskFinished 状态(DrawingMask, EVENT_RBUTTONUP) => DrawingMaskFinished,// 如果当前状态是 Init、DrawingMarkerPointFinished 或 DrawingMaskFinished 并且发生的事件是鼠标中键按下,则转换为 Resetting 状态(Init | DrawingMarkerPointFinished | DrawingMaskFinished, EVENT_MBUTTONDOWN) => Resetting,// 如果当前状态是 Resetting 并且发生的事件是鼠标中键释放,则转换为 Init 状态(Resetting, EVENT_MBUTTONUP) => Init,// 如果没有匹配到任何上述情况,则打印一条错误信息,并返回当前的绘图状态_ => {println!("Invalid state transition from {:?} with event {:?}",&drawing_state, mouse_event);drawing_state // 返回当前状态不变}}
}
相关文章:
opencv-rust 系列3: Create_mask
前言: 这里只是opencv-rust自带示例的中文注解. 略微增加了一些代码也是我在调试时用到的. 调试方法可参见前文. 一. 这个程序还是有点难度的, 关键点在于: 创建了遮罩. 直接调用一个函数, 还是很简单的.窗口事件处理. 注册窗口回调函数, 用以处理鼠标事件进程同步和互斥锁. 为…...
Go语言初识
一、Go语言概述 Go语言是为了取代C和java的地位,既要保留C的简洁,也追求java的规模化开发 并行及分布式的支持,使得开发多核及多机器集群程序如同单机一样简单 Go语言从语言级别支持协程(goroutine, 轻量级线程),Go语言…...

Android Activity SingleTop启动模式使用场景
通知栏 当用户点击通知栏中的通知时,可以使用单顶启动模式来打开对应的活动,并确保只有一个实例存在。 简单集成极光推送 创建应用 获取appkey参数 切换到极光工作台 极光sdk集成 Project 根目录的主 gradle 配置 Module 的 gradle 配置 Jpush依赖配置 配置推送必须…...
PHP 代码执行相关函数
函数 说明 示例代码 ${} 用于复杂的变量解析,通常在字符串内用来解析变量或表达式。可以配合 eval 或其他动态执行代码的功能,用于间接执行代码。 eval(${flag}); eval() 用于执行一个字符串作为 PHP 代码。可以执行任何有效的 PHP 代码片段。没有…...

五周年,继续破浪前行
五周年,TapData 再一次带着自己的“乘风破浪”大队,在一个阳光明媚的日子里,把生日过在了海上。 头顶日升日落,这条属于全体 Tap-pers 的航船,再次校准航向,在船长的带领下,驶向下一个晴好的明…...
【操作系统】Linux之进程管理一
第1关:获取进程常见属性 ret.pidgetpid(); ret.ppidgetppid(); 第2关:进程创建操作-fork pid_t pid fork(); if(pid-1) printf("创建进程失败!"); else if(pid0) printf("Children"); else printf("Parent"); …...

C语言_数据在内存中的存储
1. 整数在内存中的存储 计算机中的整数有三种2进制表示方法 :原码、反码、补码。 三种表示方式均有符号位和数值位两个部分,最高一位的是符号位,剩下的都是数值位。符号位用“0”表示“正”,用“1”表示“负”。 正数的原、反、…...

华为原生鸿蒙操作系统:我国移动操作系统的新篇章
华为原生鸿蒙操作系统:我国移动操作系统的新篇章 引言 在移动操作系统领域,苹果iOS和安卓系统一直占据主导地位。然而,随着华为原生鸿蒙操作系统的正式发布,这一格局正在发生深刻变化。作为继苹果iOS和安卓系统后的全球第三大移动…...
队列的基本操作(数据结构)
1.实验内容: 编写一个程序sqqueue.cpp,实现环形队列(假设栈中元素类型ElemType 为 char)的各种基本运算,并在此基础上设计一个程序exp4_1.cpp,完成如下功能: 2.实验步骤: (1)初始化队列q (2)判断队列q是否非空 (3…...

linux开机自启动三种方式
方式一、 1:rc.local 文件 1、执行命令:编辑 “/etc/rc.local” vi /ect/rc.local 2、然后在文件最后一行添加要执行程序的全路径。 例如,每次开机时要执行一个 hello.sh,这个脚本放在 / usr 下面,那就可以在 “/et…...

AI创作者与人类创作者的协作模式
公主请阅 1. AI创作者的崛起1.1 AI创作者的工作原理1.2 AI创作者的优势 2. 人类创作者的独特价值2.1 创造性与情感2.2 伦理与价值观2.3 文化与背景 3. AI与人类的协作模式3.1 协同创作3.2 内容编辑3.3 数据驱动的创作3.4 跨媒体协作 4. AI与人类协作的挑战4.1 技术局限性4.2 版…...

FPGA第 13 篇,使用 Xilinx Vivado 创建项目,点亮 LED 灯,Vivado 的基本使用(点亮ZYNQ-7010开发板的LED灯)
前言 在FPGA设计中,Xilinx Vivado软件是一款功能强大的设计工具,它不仅支持硬件描述语言(HDL)的开发,还提供了丰富的图形化设计界面,方便用户进行硬件设计、调试和测试。这里我们将详细介绍,如…...
Kafka文档阅读笔记之基本操作
官方资料 官方首页官方文档基本操作topic的配置参数 topic的部分操作 创建新的topic 命令样例,如下: bin/kafka-topics.sh \--bootstrap-server localhost:9092 \--create \--topic my_topic_name \--partitions 20 \--replication-factor 3 \--conf…...

Golang | Leetcode Golang题解之第506题相对名次
题目: 题解: var desc [3]string{"Gold Medal", "Silver Medal", "Bronze Medal"}func findRelativeRanks(score []int) []string {n : len(score)type pair struct{ score, idx int }arr : make([]pair, n)for i, s : …...
机器学习——元学习(Meta-learning)
元学习(Meta-learning):学习如何学习的机器学习 元学习(Meta-learning),即“学习如何学习”,是机器学习领域中一个令人兴奋且极具潜力的研究方向。它的核心目标是让机器学习系统学会高效地学习…...
【TIMM库】是一个专门为PyTorch用户设计的图像模型库 python库
TIMM库 1、引言:遇见TIMM2、初识TIMM:安装与基本结构3、实战案例一:图像分类4、实战案例二:迁移学习5、实战案例三:模型可视化6、结语:TIMM的无限可能 1、引言:遇见TIMM 大家好,我是…...

【AIGC】从CoT到BoT:AGI推理能力提升24%的技术变革如何驱动ChatGPT未来发展
博客主页: [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 💯前言💯迈向AGI的新跨越💯BoT与CoT的技术对比技术原理差异推理性能提升应用范围和通用性从错误中学习的能力总结 💯BoT的工作流程和机制初始化过程生成推…...

若依部署上线遇到的问题
一、若依部署上线的用户头像模块不能回显: 首先是后端修改部署上线后若依存储图片的本地地址 其次将上线前端配置文件中的图片相关配置给删除 二、若依部署上线后验证码不显示问题 在确保前后端请求打通后还有这个问题就是磁盘缓存问题 三、若依部署上线遇到404页…...
一个vue3的待办列表组件
一个vue3的待办列表组件, 仿企业微信的待办列表 TodoList.vue <template><div><el-input v-model"todoInput" placeholder"写下你的待办事项..." class"el-input" keyup.enter"addTodo"input-style"background-c…...

深入分析梧桐数据库SQL查询之挖掘季度销售冠军
在现代商业环境中,对销售数据的深入分析是企业决策过程中不可或缺的一部分。通过分析销售数据,企业可以识别出表现最佳的员工,从而激励团队,优化销售策略,并提高整体业绩。本文将详细介绍如何使用SQL查询来识别每个季度…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...

Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...
人工智能 - 在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型
在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型。这些平台各有侧重,适用场景差异显著。下面我将从核心功能定位、典型应用场景、真实体验痛点、选型决策关键点进行拆解,并提供具体场景下的推荐方案。 一、核心功能定位速览 平台核心定位技术栈亮…...

【大模型】RankRAG:基于大模型的上下文排序与检索增强生成的统一框架
文章目录 A 论文出处B 背景B.1 背景介绍B.2 问题提出B.3 创新点 C 模型结构C.1 指令微调阶段C.2 排名与生成的总和指令微调阶段C.3 RankRAG推理:检索-重排-生成 D 实验设计E 个人总结 A 论文出处 论文题目:RankRAG:Unifying Context Ranking…...
【题解-洛谷】P10480 可达性统计
题目:P10480 可达性统计 题目描述 给定一张 N N N 个点 M M M 条边的有向无环图,分别统计从每个点出发能够到达的点的数量。 输入格式 第一行两个整数 N , M N,M N,M,接下来 M M M 行每行两个整数 x , y x,y x,y,表示从 …...

aurora与pcie的数据高速传输
设备:zynq7100; 开发环境:window; vivado版本:2021.1; 引言 之前在前面两章已经介绍了aurora读写DDR,xdma读写ddr实验。这次我们做一个大工程,pc通过pcie传输给fpga,fpga再通过aur…...