ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Halcon与MFC联合编程:实现鼠标滚轮缩放与拖动图像的完整方案

Halcon与MFC联合编程:实现鼠标滚轮缩放与拖动图像的完整方案 简介面向Halcon与MFC联合开发者的鼠标交互示例工程解决在MFC窗口中实时缩放与拖拽Halcon图像的问题。压缩包共181个文件以133个头文件和4个cpp文件作为编码主体配合15个dll与5个lib完成运行时链接与工程引用另含预编译exe可直接运行验证整体32.75MB从Bin、Lib、Inc目录划分可清晰区分动态库、静态库与Halcon头文件便于对照项目结构理解依赖关系。实现覆盖滚轮缩放的WM_MOUSEWHEEL处理、左键拖拽平移的按下/释放事件、缩放中心跟随鼠标、图像比例保持以及防止放大超出原始尺寸或平移越界等边界处理自定义CView派生视图类用于承接鼠标消息并调用Halcon接口完成重绘。已有1382人浏览学习适合需要将机器视觉算法嵌入Windows桌面应用的开发者阅读可借此快速搭建自己的HalconMFC交互框架。 做视觉项目的人应该都有这种经历Halcon里调试好的算法放到MFC界面上图像是显示出来了但鼠标滚轮一滚没反应想放大看个缺陷细节还得重新回HDevelop里去处理。这种体验其实挺难受的因为对图像做交互缩放和拖动查看本来就是视觉软件里最基础也最高频的需求。光源调角度要看边缘细节、模板匹配要对齐特征点、缺陷确认要看纹理走向哪一步都离不开把画面放大了看、拖动着找。这篇内容就专门讲清楚Halcon和MFC联合编程下怎么用鼠标滚轮缩放图像、按住左键拖动图像。整个方案我拆成几个部分来讲为什么Halcon窗口的交互逻辑特殊、怎么把窗口和控件绑定起来、坐标换算怎么处理、缩放和拖动的完整消息处理链路以及我在实际测试中踩过的问题和解决方式。内容适用于VS2013以上版本的MFC工程Halcon版本在17以上基本都通用新版本HDevelop和旧版在窗口接口上差异不大核心逻辑可以平滑迁移。1. 这个功能为什么是视觉项目里的“基础刚需”先聊点实际的。MFC里显示Halcon图像最常规的做法是在对话框上放一个Picture Control控件然后通过HWindowControl把Halcon窗口句柄绑定到这个控件上。图像显示出来很简单但问题也随之而来Halcon自带的交互能力比如鼠标滚轮缩放、左键拖动在MFC的对话框消息循环里默认是不生效的。原因在于Halcon窗口内部有自己的事件处理逻辑但它并不监听Windows的鼠标消息你必须自己完成“Windows鼠标消息 → Halcon显示参数修改 → 重绘”这一整条链路。很多人一开始的思路是每次缩放都重新读取图像、重新设置显示尺寸。这个思路如果图像小还好说一旦处理的是几千乘几千的大图每次滚轮都重新读图界面卡顿会非常明显而且逻辑上也不对。缩放本质改的是“显示窗口对应的图像区域”不是图像数据本身。Halcon里对应的核心算子就是SetPart它能直接把显示窗口映射到图像的一个矩形区域上实现无损缩放的视觉效果。再有一个痛点是坐标换算。MFC的鼠标消息给的是控件客户区的像素坐标比如鼠标在控件中心点坐标可能是(200, 150)但Halcon窗口里对应的图像坐标是多少如果不清除Halcon的坐标体系、不搞清楚窗口尺寸和图像尺寸的对应关系缩放中心就会乱跳拖动手感也会很别扭。这些基础逻辑不先理清楚后面的代码就是一锅粥。2. 环境筹备与界面布局窗口绑定的正确姿势2.1 Halcon窗口如何“嵌”进MFC对话框Halcon和MFC的联合编程里窗口绑定是第一步。打开的Halcon窗口句柄必须和对话框里的Picture Control控件关联起来。常规的初始化流程分三步第一步在对话框的头文件里声明一个HTuple类型的窗口句柄变量比如m_hHalconWnd。第二步在OnInitDialog里获取Picture Control的客户区矩形拿到控件的句柄。第三步调用OpenWindow(0, 0, width, height, parent_handle, visible, , m_hHalconWnd)打开一个Halcon窗口其中parent_handle就是Picture Control的句柄。需要注意的是OpenWindow里传入的窗口宽高是控件的客户区尺寸单位是像素。如果你在资源编辑器里调整了控件大小代码里的宽高需要同步更新否则Halcon窗口会跟控件对不齐。有些工程会偷懒直接写死宽高比如640×480界面一旦DPI缩放或者控件拉伸显示就错位了。我自己的习惯是写一个GetClientRect动态获取尺寸再打开窗口这样窗口在自适应布局里也不会出问题。2.2 SetPart理解Halcon显示区域的核心机制在写鼠标交互代码之前必须先弄明白SetPart到底在做什么。SetPart(WindowHandle, Row1, Column1, Row2, Column2)设定的内容是当前Halcon窗口显示的图像区域行方向从Row1到Row2列方向从Column1到Column2。这五个参数里Row1、Column1是显示区域的左上角在图像坐标系里的坐标Row2、Column2是右下角坐标。这里有一个极其重要的认知SetPart的参数是图像坐标不是像素坐标而且可以是浮点数。什么意思呢如果当前图像是1280×1024窗口大小是640×512那么默认情况下设置SetPart(0, 0, 1024, 1280)图像会完整地显示在窗口里。这里的行和列是图像的行列号不是窗口的行列号。缩放的过程其实就是不断修改Row1、Column1、Row2、Column2的值让窗口显示的图像区域变小放大或变大缩小。举一个实例。完整显示时Row10、Column10、Row21024、Column21280。如果我想把图像中心区域放大两倍那就应该把显示区域缩小到原来的一半比如Row1256、Column1320、Row2768、Column2960。这样窗口内看到的就是原图中心区域放大后的效果。缩放中心不同Row1、Column1的偏移也不同这也正是“以鼠标所在位置为缩放中心”的实现基础。2.3 把图像区域作为成员变量保存下来既然缩放和拖动本质是修改显示区域那么这四个数值必须在整个交互过程中被持续跟踪。我建议在对话框类里定义四个double类型成员变量double m_dRow1; // 当前显示区域左上角行坐标 double m_dCol1; // 当前显示区域左上角列坐标 double m_dRow2; // 当前显示区域右下角行坐标 double m_dCol2; // 当前显示区域右下角列坐标每次显示新图像时根据图像尺寸设置这四个值来显示全图之后鼠标滚轮、左键拖动事件里做的所有计算最后都会更新这四个值再调用SetPart重绘。这套设计的好处是把“显示状态”和“交互逻辑”解耦了后续不管是增加缩放比例显示、恢复全图还是其他功能都只需要操作这四个变量思路会非常清爽。还要考虑图像的初始加载。在MFC里显示一幅Halcon图像常规流程是ReadImage读取图像然后GetImageSize获取图像宽高接着根据图像宽高设置初始显示区域HTuple hv_Width, hv_Height; GetImageSize(ho_Image, hv_Width, hv_Height); m_dRow1 0; m_dCol1 0; m_dRow2 hv_Height.D(); m_dCol2 hv_Width.D(); SetPart(m_hHalconWnd, m_dRow1, m_dCol1, m_dRow2, m_dCol2); DispObj(ho_Image, m_hHalconWnd);这段代码的含义是把显示区域设为整幅图像的范围然后把图像显示出来。后续的缩放和拖动操作都是基于这套初始状态来改变显示区域。有一个细节值得注意图像的行列和宽高对应关系不要搞反GetImageSize返回的Width对应列方向ColumnHeight对应行方向Row。在二维坐标系里图像的行方向是垂直的所以Row1是上边界、Row2是下边界。3. 坐标换算所有交互操作的地基3.1 鼠标像素坐标怎么变成图像坐标MFC里鼠标消息给的是控件客户区的像素坐标这个坐标的原点位于控件左上角x轴向右y轴向下。而Halcon里图像坐标的原点在图像左上角Row轴向下Column轴向右。虽然方向完全一致但Scale比例尺不同因为窗口尺寸和显示区域范围基本不会完全一致。如果当前窗口的宽度是w像素高度是h像素显示区域是(m_dRow1, m_dCol1)到(m_dRow2, m_dCol2)那么鼠标位置(mouse_x, mouse_y)对应的图像坐标(img_row, img_col)可以这样换算double img_col m_dCol1 (m_dCol2 - m_dCol1) * mouse_x / w; double img_row m_dRow1 (m_dRow2 - m_dRow1) * mouse_y / h;这个公式的本质是线性比例换算。鼠标在窗口中的相对位置mouse_x / w乘以当前显示区域的宽度m_dCol2 - m_dCol1就得到相对于显示区域左上角的列偏移再加上显示区域左上角的列坐标m_dCol1就是鼠标在图像上的列坐标。行方向同理。这段代码是后面所有缩放和拖动逻辑的基础务必理解清楚再往下写。3.2 缩放中心不跑偏的计算方法很多初学时候写的缩放逻辑缩放中心固定在图像中心鼠标滚轮滚到哪儿图像中心的点始终不动。这种实现简单但手感很差。更自然的体验是鼠标指到图像上的哪个点缩放后那个点就待在鼠标当前位置不动。要做到这一点缩放前后需要保持“鼠标所指的图像点”在窗口中的位置不变。具体做法是在缩放前记录鼠标位置对应的图像坐标(img_row, img_col)缩放后更新了显示区域再反推出显示区域左上角坐标。反推公式如下// 以鼠标位置为中心scale_factor表示缩放倍率大于1为放大小于1为缩小 // 放大显示的行列范围变小 double new_height (m_dRow2 - m_dRow1) / scale_factor; double new_width (m_dCol2 - m_dCol1) / scale_factor; // 保持鼠标所指图像点不动反推新显示区域的左上角 m_dRow1 img_row - new_height * mouse_y / h; m_dCol1 img_col - new_width * mouse_x / w; m_dRow2 m_dRow1 new_height; m_dCol2 m_dCol1 new_width;这里面的逻辑推导也不复杂。缩放后鼠标所在窗口位置对应的图像点应该还是(img_row, img_col)那么鼠标位置在窗口中的相对位置(mouse_y / h, mouse_x / w)乘以新显示区域的高宽就是鼠标相对于新显示区域左上角的偏移用图像点坐标减去这个偏移自然就得到了新显示区域的左上角坐标。算完之后把边界限制在图像范围内再调用SetPart刷新即可。3.3 边界限制防止显示区域跑出图像范围边界限制经常被忽略但实际用起来没有边界限制滚动几次滚轮图像就不知道拖到哪儿去了显示区域完全超出了图像范围屏幕上只剩一片空白。边界限制的核心思路是显示区域的四个边界必须落在图像范围内。对应到代码需要获取图像尺寸img_rows和img_cols然后用std::max和std::min做限制// 显示区域宽度不能超过整幅图像 if (m_dRow2 - m_dRow1 img_rows) { m_dRow1 0; m_dRow2 img_rows; } if (m_dCol2 - m_dCol1 img_cols) { m_dCol1 0; m_dCol2 img_cols; } // 边界超出时收回 if (m_dRow1 0) { m_dRow2 - m_dRow1; m_dRow1 0; } if (m_dCol1 0) { m_dCol2 - m_dCol1; m_dCol1 0; } if (m_dRow2 img_rows) { m_dRow1 - (m_dRow2 - img_rows); m_dRow2 img_rows; } if (m_dCol2 img_cols) { m_dCol1 - (m_dCol2 - img_cols); m_dCol2 img_cols; }这里有个细节要注意m_dRow1和m_dCol1可能变成负数因此这两个变量必须用double类型不能用int否则小数部分直接截断缩放中心的位置会越来越偏手感完全不对。4. 滚轮缩放以鼠标为中心才“跟手”4.1 响应WM_MOUSEWHEEL消息MFC对话框类里添加滚轮消息响应在BEGIN_MESSAGE_MAP里添加ON_WM_MOUSEWHEEL()宏然后在头文件里声明afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);。这里面有几个容易踩坑的地方第一zDelta是滚轮滚动的增量正数表示向上滚动放大负数是向下滚动缩小。每次滚动一般是120的倍数所以实际倍率计算公式为pow(kScaleStep, zDelta / 120.0)这样做的好处是滚一格就按固定系数缩放滚得快慢不影响缩放幅度。第二pt是鼠标在屏幕坐标下的位置需要先ScreenToClient(pt)转换成控件客户区坐标。如果忘了这一步缩放中心会严重偏移尤其在多显示器环境下尤其明显。第三当鼠标悬停在Picture Control之外时不应该触发缩放。判断方法是ChildWindowFromPoint或者直接判断pt是否在控件客户区矩形内。我这里用的是直接判断矩形包含关系CRect rectWnd; GetDlgItem(IDC_HALCON_WINDOW)-GetClientRect(rectWnd); if (!rectWnd.PtInRect(pt)) return TRUE; // 不处理4.2 缩放倍率选择的经验值缩放倍率选多少合适一开始我用的1.5倍滚一格图刷地一下就放得很大图像细节不好定位后来改成1.1倍又觉得太慢想放大到想看的位置要滚很多圈。最终长期使用的是1.2倍手感接近主流看图软件。当然这个因人而异建议把缩放系数做成一个成员变量后续调整只需要改一处。完整缩放部分的逻辑整理如下BOOL CMyHalconDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { if (m_hHalconWnd NULL) return CDialogEx::OnMouseWheel(nFlags, zDelta, pt); CRect rectWnd; GetDlgItem(IDC_HALCON_WINDOW)-GetClientRect(rectWnd); ScreenToClient(pt); if (!rectWnd.PtInRect(pt)) return TRUE; double scale_factor pow(1.2, zDelta / 120.0); double mouse_x pt.x; double mouse_y pt.y; double w rectWnd.Width(); double h rectWnd.Height(); if (w 0 || h 0) return TRUE; // 鼠标位置对应的图像坐标 double img_col m_dCol1 (m_dCol2 - m_dCol1) * mouse_x / w; double img_row m_dRow1 (m_dRow2 - m_dRow1) * mouse_y / h; // 缩放后显示区域宽高 double new_width (m_dCol2 - m_dCol1) / scale_factor; double new_height (m_dRow2 - m_dRow1) / scale_factor; // 反推左上角 m_dCol1 img_col - new_width * mouse_x / w; m_dRow1 img_row - new_height * mouse_y / h; m_dCol2 m_dCol1 new_width; m_dRow2 m_dRow1 new_height; ClampDisplayRegion(); // 边界限制 SetPart(m_hHalconWnd, m_dRow1, m_dCol1, m_dRow2, m_dCol2); DispObj(m_hoImage, m_hHalconWnd); return TRUE; }这里的ClampDisplayRegion就是我前面写的边界限制函数。每次修改了显示区域都必须调用它否则很快图像就会跑飞。5. 按住拖动容易踩坑的捕获与边界处理5.1 按下、移动、释放三个消息的状态机拖动的基础是三个鼠标消息的配合左键按下开始拖动、鼠标移动更新显示区域、左键释放结束拖动。我需要一个标志位来标识当前是否处于拖动状态比如m_bDragging。按下左键时把它设为TRUE并记录鼠标按下时的位置和当时的显示区域移动时如果标志位为TRUE就计算位移并更新显示区域释放时把标志位设回FALSE。这里有个关键点移动过程中显示区域的变化量应该由“按下时的显示区域”减去“当前鼠标位置和按下位置的偏移量”得到而不是在当前显示区域上累加。两种方式看似等价实际上累加方式会积累浮点误差拖动的距离越久图像跟手的偏差越大。正确做法是在按下时暂存初始状态void CMyHalconDlg::OnLButtonDown(UINT nFlags, CPoint point) { m_bDragging TRUE; m_ptDragStart point; m_dDragStartRow1 m_dRow1; m_dDragStartCol1 m_dCol1; m_dDragStartRow2 m_dRow2; m_dDragStartCol2 m_dCol2; SetCapture(); // 捕获鼠标防止拖出控件后丢失消息 CDialogEx::OnLButtonDown(nFlags, point); }5.2 移动计算里的方向转换移动处理里最容易搞反的是方向。鼠标向右移动时图像内容是向左移动的就像你在桌面上挪动一张纸纸跟着鼠标的方向走是“抓起来移动”的模式眼睛看到的图像内容会反向移动。这个和用户习惯有关大部分视觉软件采用的模式是按住左键拖动图像内容跟随鼠标移动方向移动。如果希望“图像跟随鼠标移动”那么鼠标向右移动显示区域的左上角Column应该减小因为显示区域向左移动图像看起来就是向右移。换算成公式void CMyHalconDlg::OnMouseMove(UINT nFlags, CPoint point) { if (m_bDragging) { double dx point.x - m_ptDragStart.x; // 正数鼠标向右 double dy point.y - m_ptDragStart.y; // 正数鼠标向下 double w GetControlWidth(); double h GetControlHeight(); double col_per_pixel (m_dDragStartCol2 - m_dDragStartCol1) / w; double row_per_pixel (m_dDragStartRow2 - m_dDragStartRow1) / h; m_dCol1 m_dDragStartCol1 - dx * col_per_pixel; m_dRow1 m_dDragStartRow1 - dy * row_per_pixel; m_dCol2 m_dCol1 (m_dDragStartCol2 - m_dDragStartCol1); m_dRow2 m_dRow1 (m_dDragStartRow2 - m_dDragStartRow1); ClampDisplayRegion(); SetPart(m_hHalconWnd, m_dRow1, m_dCol1, m_dRow2, m_dCol2); DispObj(m_hoImage, m_dHalconWnd); } CDialogEx::OnMouseMove(nFlags, point); }这里dx * col_per_pixel的核心含义是窗口像素位移映射到图像坐标的位移量。因为显示区域可能是缩放的窗口移动一个像素图像上对应的移动量就是显示区域宽度除以窗口宽度的商。这个换算系数随着缩放倍数变化而变化所以必须在按下时锁定否则拖动过程中图像会自己抖动。5.3 鼠标捕获和释放的规范处理拖动过程中鼠标大概率会移出Picture Control的范围甚至移出整个对话框窗口。如果不做捕获Windows会在鼠标离开控件时停止向该控件发送鼠标消息拖动就会断开。调用SetCapture()之后只要左键还没释放所有鼠标消息都会发给这个窗口哪怕鼠标跑到屏幕外面。对应的在OnLButtonUp里必须调用ReleaseCapture()释放捕获否则后续其他控件就收不到鼠标消息了。这个配对很容易忘记我自己的项目里就遇到过拖动过后按钮点击没反应的情况查了半天才意识到是捕获没有释放导致的。另外还有一个跟焦点相关的坑Halcon窗口被嵌入Picture Control之后鼠标在窗口内点击时如果MFC的对话框没有正确设置焦点可能会触发Halcon窗口自身的焦点处理逻辑导致窗口边框闪烁。一个通用的解决办法是在初始化时调用SetWindowPos把Picture Control的焦点转移走或者在鼠标按下后对对话框调用SetFocus()。这个不是必经步骤但不少机器上确实会出现焦点闪烁问题做一下更稳妥。6. 实测中的高频问题坐标错位、闪烁和DPI缩放的影响6.1 图像边缘出现白边或区域越界这个问题的根源多半是边界限制没做彻底。比如缩放时显示区域被限制到了图像范围内但宽高比变了窗口内就会出现一部分空白区域。Halcon的窗口默认背景色是黑色但有些工程改过背景色显示出来就是一条白边或者黑边。解决办法是限制显示区域的宽高比和窗口的宽高比保持一致。如果窗口宽高比是4:3那么显示区域的宽高比也必须是4:3否则图像会变形或者留边。在缩放计算时需要根据窗口宽高比先计算目标显示宽高再带入计算。double ratio_wnd w / h; double ratio_img img_cols / img_rows; // 根据窗口比例和图像比例确定初始显示区域然后在这个基础上做缩放在实际项目中我建议初始显示时做一个“适应窗口”的计算如果图像宽高比大于窗口宽高比则以图像宽度为准高度方向留白反之以高度为准。留白的区域在Halcon窗口里会用背景色填充整体观感更干净。6.2 高DPI显示下鼠标坐标偏移Win10及以上系统、笔记本缩放比例设置为125%或150%时鼠标消息里拿到的坐标和控件实际像素坐标之间会有一个DPI缩放系数。比如系统缩放比例为125%逻辑坐标和物理坐标的比例就是1:1.25。如果不做处理鼠标在控件上点击后缩放中心会明显往左上偏移。处理方式有两种第一种是禁用自动DPI缩放在工程清单文件里声明dpiAwaretrue/dpiAware让应用程序拿到真实的物理像素坐标第二种是动态获取DPI缩放系数做坐标换算后再计算。第一种方法简单粗暴但高DPI屏幕上文字和控件会模糊第二种更通用代码里可以这样获取缩放系数UINT dpi GetDpiForWindow(GetSafeHwnd()); double scale_factor dpi / 96.0;拿到缩放系数后把鼠标消息里的pt坐标除以缩放系数再参与计算。这个适配在开发机上可能根本看不出来但你的软件发给客户客户的笔记本几乎都是125%缩放没有做适配整个交互就是歪的。6.3 缩放和拖动时的闪烁问题如果连续滚动滚轮SetPart加DispObj的组合会导致图像闪烁尤其是在图像较大、显示频率较高的时候。Halcon窗口默认的显示模式有Flush机制连续刷新时会出现明显的全屏重绘。解决办法是调整窗口的显示缓存策略在初始化时设置SetWindowParam(flush, false)然后在DispObj之后手动调用FlushBuffer。更简洁的方案是使用SetCheck配合〜flush参数控制缓存刷新。具体代码如下SetWindowParam(m_hHalconWnd, flush, false); // 交互过程中 DispObj(m_hoImage, m_hHalconWnd); FlushBuffer(m_hHalconWnd);实测下来加了缓存策略之后滚轮连续缩放基本没有闪烁感拖动过程中的流畅度也明显改善。如果图像特别大还可以考虑降低交互过程中的显示分辨率只在鼠标松开时做一次全分辨率重绘不过这个属于性能优化的进阶方案一般项目用不到。6.4 窗口句柄未初始化导致崩溃一个隐蔽但致命的崩溃点是对话框初始化时如果Halcon窗口还没有成功打开任何SetPart或DispObj调用都会直接抛异常。这种情况在切换图像、重新加载图像的流程里容易发生——图像加载到了新线程但窗口句柄已经被关闭了。我的建议是每一处调用SetPart和DispObj的地方都先判断窗口句柄是否有效if (m_hHalconWnd ! NULL) { SetPart(m_hHalconWnd, m_dRow1, m_dCol1, m_dRow2, m_dCol2); DispObj(m_hoImage, m_hHalconWnd); }另外在关闭窗口消息里要显式关闭Halcon窗口句柄CloseWindow否则窗口对象会残留程序退出时可能触发Halcon运行时错误。很多人习惯依赖系统回收但Halcon的窗口资源有时不会在对话框销毁时自动释放规范处理是写一个OnDestroy在对话框销毁前去关闭窗口。7. 常用调试技巧怎么快速验证坐标换算对不对坐标换算是整个缩放和拖动功能里最容易出错的部分而且错误往往是“能跑但手感不对”的软性错误调试起来比较麻烦。我分享两个自己常用的调试方法。第一个方法是在图像上叠加显示一个十字交叉线。具体做法是在DispObj之后再用DispCross在鼠标位置画一个十字然后把十字的坐标和鼠标在图像上的坐标打印出来。拖动、缩放之后如果十字始终能准确覆盖鼠标所指的图像位置说明坐标换算正确十字偏离的话偏离的方向和大小能直接告诉你换算公式哪里出了问题。第二个方法是用Halcon自带的窗口控制里的小工具辅助验证。在HDevelop里你可以手动打开图像、手动设置SetPart观察不同参数值下显示区域的变化。实际操作一遍之后你会对Row1、Column1这些参数从抽象变为具象返回来调试MFC代码时会清晰很多。毕竟SetPart的行为和参数关系在HDevelop里就能完整体验MFC里只是把这份体验用代码自动化了。还有一个实用技巧在拖动和缩放处理函数里加上日志输出把缩放前后的m_dRow1、m_dCol1、m_dRow2、m_dCol2以及鼠标的窗口坐标、图像坐标一起打印出来。一组数据对不上直接对照就能定位是哪一步算错了。我先花几分钟把日志调通再关掉后面排查效率会高很多。8. 后续扩展思路不止缩放拖动还能做更多缩放和拖动这两个基础交互打通之后其实已经把“图像显示交互”的底座搭好了。在同样的坐标换算和消息处理框架上还能继续扩展出不少实用功能比如矩形框选区域。鼠标按下时记录起点移动中绘制矩形松开后根据起点和终点坐标计算出图像上的矩形区域用于ROI选择。在这个框架里矩形框的起点终点直接从拖动的起点和当前鼠标位置换算成图像坐标即可逻辑天然兼容。再比如像素坐标实时显示。鼠标在图像上移动时把当前鼠标位置换算成图像坐标再通过GetGrayval获取该点的灰度值或者RGB值显示在界面上。视觉算法调试时查看特定位置的像素信息非常有用配合缩放拖动一起用使用体验接近商业视觉软件。还可以做图像标注、测量工具、模板区域调整等进阶功能底层依赖的其实都是“鼠标像素坐标 ↔ 图像坐标”的转换关系。所以花时间把基础交互做扎实后面扩展新功能时思路会顺畅很多。回到最初的问题为什么要费劲在MFC里去实现鼠标缩放和拖动因为视觉软件的真实使用场景从来都不是把图像一次性完整显示出来就算完成。操作者是要在图像里找特征、量尺寸、看细节的这些动作都依赖自然的图像交互。把这套基础能力做扎实你的软件才算真正“能用”和“好用”。本文还有配套的精品资源点击获取
返回列表