资讯动态

Halcon形状匹配+MFC上位机:可双击exe模板匹配Demo实战

发布时间:2026/10/23 8:59:34 来源:尧图企业网站定制
简介面向机器视觉初学者与C桌面开发者的halconmfc模板匹配demo聚焦在Windows界面中完成基于形状、形状缩放与灰度的模板匹配验证。工程把Halcon图像处理能力嵌入MFC对话框提供图像加载、匹配模式选择、参数设置、执行匹配及位置与相似度结果展示等交互适合学习工业视觉定位、目标识别与GUI集成的基础流程。压缩包共27个文件以8个h和6个cpp源码为主配合ico、rc等界面资源vcxproj、sln等VS工程配置以及jpg预览图、exe可执行程序和说明文档rar整体约11.45MB。从目录模块看参数设置、形状操作、结果封装与主对话框逻辑划分较清晰便于对照编译、调试和二次修改。已有1246人学习可作为Halcon与MFC联合开发模板匹配功能的入门参考。1. 一个能双击运行的 exe这个 HalconMFC 模板匹配 demo 到底解决什么问题某五金件产线上工件在传送带上随机偏转角度大概在正负十五度之间晃机械手要过去抓。算法同事在 HDevelop 里半小时就调通了create_shape_model建模板、find_shape_model拿 Row/Column/Angle 一气呵成。可真要交付到工位操作工不可能打开 HDevelop 点运行。问题于是从“怎么匹配”变成“怎么把匹配包成一个能双击的 exe”——这正是 Halcon MFC 模板匹配 demo 的位置。它做的事很具体把形状匹配的建模、查找、结果显示、参数调节塞进一个 MFC 对话框程序里对外输出一个四元组Row、Column、Angle、Score再把匹配到的轮廓叠画在图上。适合做课设毕设的、刚从脚本过渡到上位机的、以及要给现场交付独立程序的人如果你只做纯算法研究或者习惯 Python 调库这套东西对你价值不大。2. 工程骨架把 Halcon 算子塞进 MFC 的三条路与一次编译配置2.1 HDevEngine、导出 C、混合调用先选一条这一步选错了后面全是返工。三种接法我都在项目里用过差别不在性能在“谁改算法”这件事上。接法做法优点代价适合HDevEngine运行时加载 .hdev用HDevEngine执行过程算法同事改脚本不用重编 exe多带一个引擎 dll报错是脚本行号每次调用有解析开销算法还在频繁迭代、现场要临时调参导出 CHDevelop 里“文件→导出 C”生成 .cpp执行最快无额外依赖改一行脚本就要重新编译整个工程算法已经冻结、追求稳定交付混合建模和调参走脚本查找和结果显示走 C兼顾两边接口要自己设计状态要管好我一般会选这个Demo 这种体量直接导出 C 最省事因为模板匹配的调用链很短没必要为它背一个脚本引擎。但如果你要做“现场工程师拿着 hdev 自己调对比度”的功能那就得走 HDevEngine并且把脚本里的过程名、输入输出参数用SetProcedure、SetInputIconicParamObject这类接口约定死否则脚本一改接口exe 直接报找不到过程。2.2 VS 工程配置位数、运行时库、头文件顺序Halcon 的库和你的工程必须位数一致这一条没有例外。新版本基本只提供 64 位所以 MFC 工程从新建那一刻就该选 x64别等编译报一堆LNK2019再回头改。属性页里要落的东西位置项值C/C → 常规附加包含目录$(HALCONROOT)\include、$(HALCONROOT)\include\halconcpp链接器 → 常规附加库目录$(HALCONROOT)\lib\$(HALCONARCH)链接器 → 输入附加依赖项halconcpp.libC/C → 代码生成运行时库与 Halcon 保持一致一般/MDRelease、/MDdDebugMFC 使用方式—在共享 DLL 中使用 MFCHALCONROOT和HALCONARCH这两个环境变量是安装时写进系统的用$(HALCONROOT)引用比自己写死路径靠谱换机器不用改工程。头文件包含顺序上有一个老坑windows.h里的min/max宏会和标准库打架而 MFC 又必然带进windows.h// pch.h或 stdafx.h里包含顺序不要随意调换 #define NOMINMAX // 挡掉 windows.h 的 min/max 宏 #include windows.h #include atlconv.h // 后面要用 CT2A 做编码转换 #include halconcpp/HalconCpp.h // 放在 windows.h 之后避免类型重定义 using namespace HalconCpp; // 只在 cpp 里用别写进头文件污染全局NOMINMAX不加std::min之类的调用会莫名其妙编译不过using namespace写进头文件则会让所有包含它的 cpp 都被拖进 Halcon 的命名空间后面和 MFC 的CRect、CPoint撞名是迟早的事。部署时别忘了把运行时 dll 拷到 exe 同级目录halcon.dll、halconcpp.dll是必须的走 HDevEngine 还要加引擎那个 dll。我在项目里放了个拷贝脚本打包前一键执行# 打包前把运行时 dll 拉到输出目录避免现场机器装没装 Halcon 的玄学问题 OUTbin/Release HALCON_BIN$HALCONROOT/bin/$HALCONARCH cp $HALCON_BIN/halcon.dll $OUT/ cp $HALCON_BIN/halconcpp.dll $OUT/ # 只有用 HDevEngine 才需要下面这两个 # cp $HALCON_BIN/hdevenginecpp.dll $OUT/ # cp $HALCON_BIN/hdevengine.dll $OUT/脚本本身没什么技术含量价值在于把“哪些 dll 是必需的”这件事固化下来。现场机器往往装的是运行版而不是开发版多拷一个 dll 顶多占几兆少拷一个就是启动即崩。2.3 线程模型UI 线程只负责画算子跑在工作线程MFC 的死穴是 UI 线程不能阻塞超过几十毫秒而一次find_shape_model在高分辨率图、多角度、多层金字塔的情况下跑到几十甚至上百毫秒很常见。连续采图时直接在OnBnClickedStart里循环调用界面必卡任务管理器里那个“未响应”的标题就是血泪经验。更麻烦的是 Halcon 窗口有线程亲和性OpenWindow在哪个线程开的就只能在哪个线程操作它。跨线程去DispObj轻则不显示重则直接崩。我一般这么切// 自定义消息工作线程算完把结果丢回 UI 线程去显示 #define WM_MATCH_RESULT (WM_USER 100) UINT CMatchDemoDlg::MatchThreadProc(LPVOID pParam) { CMatchDemoDlg* pDlg static_castCMatchDemoDlg*(pParam); HObject ho_Image; HTuple hv_Row, hv_Column, hv_Angle, hv_Score; while (pDlg-m_bRunning) { try { // 1. 取图这里换成你的相机采集或文件读取 ReadImage(ho_Image, HTuple(data/part_01.png)); // 2. 匹配hv_ModelID 在 UI 线程建好后传进来建一次用一辈子 FindShapeModel(ho_Image, pDlg-m_hvModelID, HTuple(-15).TupleRad(), HTuple(30).TupleRad(), 0.6, 1, 0.5, least_squares, 0, 0.8, hv_Row, hv_Column, hv_Angle, hv_Score); // 3. 只传结果和图像句柄不碰任何窗口 pDlg-m_hoLastImage ho_Image; // HObject 是引用计数赋值是安全的 pDlg-PostMessage(WM_MATCH_RESULT, 0, 0); } catch (HException ex) { // Halcon 异常必须接住否则线程直接终止 pDlg-PostMessage(WM_MATCH_RESULT, 1, 0); } Sleep(10); } return 0; }这段的关键点有三个FindShapeModel的NumLevels传 0 表示用建模时定好的层数不要在这里另设一个值SubPixel选least_squares比interpolation稳代价是慢一点点HObject的赋值是浅拷贝加引用计数所以把图像存到对话框成员上不会带来一次完整的内存拷贝但也意味着你在工作线程里对同一个ho_Image再ReadImage一次主线程正在显示的那张会跟着变——想要每帧独立就CopyObj一份。UI 线程那边只做三件事接消息、DispObj、画十字和矩形框。这样即使匹配跑到 200 毫秒界面拖动、缩放依然跟手。3. 模板训练与查找参数八个建模参数和四个查找参数怎么定3.1create_shape_model的参数一个个说清楚形状匹配的建模参数直接决定“能不能找到”查找参数只决定“找得多准”。顺序别搞反。参数含义常用取值调错的表现NumLevels金字塔层数auto或 06太大丢小特征太小慢AngleStart起始角弧度按工件实际偏转定范围不够直接找不到AngleExtent角度跨度弧度同上同上AngleStep角度步长弧度auto或按半径算太粗定位抖太细建模慢Optimization模板点优化方式auto、point_reduction_high高优化会牺牲一点点精度Metric极性use_polarity光照反了就全丢Contrast边缘对比度阈值auto或 2060太高模板点太少MinContrast查找时最低对比度建模值的 1/21/3太高现场找不到Metric这个参数是最容易被忽略的。默认use_polarity要求待查图像里边缘的明暗方向和模板一致如果现场打光方式变了或者工件翻面边缘极性反了score 会直接掉到 0.1 以下你还以为是角度范围设小了。另一种是ignore_local_polarity允许局部极性变化鲁棒性更好但误匹配率上升我在有反光铝件上用过得配合提高MinScore一起用。// 建模先框 ROI再做域缩减最后建形状模板 HObject ho_ModelRegion, ho_ModelImage; GenRectangle1(ho_ModelRegion, 120.0, 200.0, 380.0, 460.0); // 行1,列1,行2,列2 ReduceDomain(ho_Image, ho_ModelRegion, ho_ModelImage); // 只在 ROI 内取边缘 HTuple hv_ModelID; CreateShapeModel(ho_ModelImage, HTuple(0), // NumLevels0让 Halcon 自己定 HTuple(-15).TupleRad(), // AngleStart注意单位是弧度 HTuple(30).TupleRad(), // AngleExtent覆盖 -15°~15° HTuple(auto), // AngleStep HTuple(auto), // Optimization HTuple(use_polarity), // Metric HTuple(auto), // Contrast HTuple(25), // MinContrast现场光照波动大就调小 hv_ModelID);ReduceDomain这一步不能省。整幅图去建模板背景里的传送带纹路、料盘边缘全会被当成特征点模板点数暴涨建模慢、查找更慢而且容易在背景相似的地方误匹配。ROI 尽量贴着工件外轮廓留 510 像素余量。MinContrast单列出来说是因为它决定了“现场能不能找到”。建模时的Contrast是按当前这张图的对比度自动算的换一批料、换一个班次的光照对比度可能掉三成。这时候MinContrast如果还等于建模值那些边缘就被过滤掉模板点剩不下几个。我一般把它压到Contrast的三分之一左右宁可多留点噪声点也别让模板变成空壳。3.2 金字塔层数和角度步长不要让auto替你做决定auto在大多数场景下是合理的但它有个隐含前提——你的模板 ROI 大小适中。ROI 特别大比如 800×600 的整块钣金时auto给出的层数会偏高最顶层只剩几十个点粗定位直接飘ROI 特别小比如 30×30 的字符层数又会偏低查找慢得让人怀疑人生。角度步长有个可以手算的经验公式模板上离中心最远的边缘点半径记作 r旋转 Δθ 后这个点的位移约等于 r·Δθ。要求这个位移不超过 1 个像素就得到 Δθ ≤ 1/r弧度。半径 150 像素的模板Δθ 差不多 0.0067 弧度也就是 0.38 度。// 由模板 ROI 的等效半径反推角度步长比 auto 更贴合实际尺寸 double w 260.0, h 160.0; // ROI 宽高像素 double r 0.5 * sqrt(w * w h * h); // 最远边缘点半径 double stepRad 1.0 / r; // 旋转位移不超过 1 像素 double stepDeg stepRad * 180.0 / 3.14159265; stepDeg stepDeg * 0.7; // 留 30% 余量 stepDeg max(0.1, min(1.0, stepDeg)); // 夹到工程可用区间 HTuple hv_AngleStep HTuple(stepDeg).TupleRad();算完之后可以反过来验证把AngleStep设成这个值建一次模板再用同一张图在角度范围的边界处比如 14.5 度去查score 应该还在 0.8 以上。如果掉得厉害说明步长还是粗了。这个“边界自测”是我每次建完模板必做的一步比看任何文档都直观。层数这边我的习惯是建完模板后打印一下每层的点数// 检查金字塔各层剩下多少模板点顶层太少就手动降层数 HTuple hv_NumLevels, hv_Points; GetShapeModelContours(ho_ModelContours, hv_ModelID, 1); // 取第 1 层轮廓 // 更直接的办法重建一次显式指定层数对比匹配耗时和稳定性顶层点数低于 20 个粗定位基本靠运气这时候要么把NumLevels手动减 12 层要么扩大 ROI 让特征更丰富。别嫌麻烦这一步省下的时间会在现场调试时十倍还回来。3.3find_shape_model分数、重叠和贪心度查找参数里真正需要动的是四个MinScore、NumMatches、MaxOverlap、Greediness。MinScore是门槛低于它的候选直接丢弃。设太高光照一变就报“未找到”设太低背景里随便一块纹理都能凑到 0.4。经验值是 0.60.7 起步等批量验证过 score 分布之后再收紧。MaxOverlap处理叠件。工件挨在一起时两个匹配结果可能重叠 80% 以上其实是同一个目标被重复报了。设成 0.30.5 能有效去重但设太小会把真正紧挨着的两个工件误杀。Greediness是搜索的“贪心程度”0 最彻底最慢1 最快但可能漏检默认 0.9。精密对位场景我一般降到 0.70.8多花 20% 时间换稳定性值。// 查找并解析结果注意返回的是四个 HTuple长度等于匹配个数 HTuple hv_Row, hv_Column, hv_Angle, hv_Score; FindShapeModel(ho_Image, hv_ModelID, HTuple(-15).TupleRad(), HTuple(30).TupleRad(), HTuple(0.6), // MinScore HTuple(3), // NumMatches最多找 3 个 HTuple(0.4), // MaxOverlap去掉重叠候选 HTuple(least_squares), // SubPixel亚像素精度 HTuple(0), // NumLevels0沿用建模层数 HTuple(0.8), // Greediness hv_Row, hv_Column, hv_Angle, hv_Score); // 结果按 Score 降序排列取第 0 个就是最优 for (int i 0; i hv_Score.Length(); i) { double row hv_Row[i].D(); // Row 是行坐标也就是 Y double col hv_Column[i].D(); // Column 是列坐标也就是 X double ang hv_Angle[i].D(); // 弧度顺时针为正 double scr hv_Score[i].D(); // 这里做你的坐标转换、发给 PLC }亚像素模式选least_squares它在初步定位后用最小二乘拟合边缘重复精度能到 0.02 像素级别interpolation快一些但精度差一档none只在极端追求速度时用。这个参数和Greediness是速度与精度这组矛盾的两个旋钮别同时往快的那头拧。4. 显示与坐标从 HObject 到 Picture Control 的像素对齐4.1 把 Halcon 窗口嵌进控件父句柄别传错Halcon 的窗口本质上是个子窗口OpenWindow的第五个参数就是父窗口句柄。传对话框的m_hWnd图像会盖住所有控件传 Picture Control 的句柄图像才会老老实实待在框里。void CMatchDemoDlg::OpenHalconWindow() { // 重开前必须先关否则每切换一次分辨率就漏一个窗口句柄 if (m_hvWindowHandle.Length() 0) { CloseWindow(m_hvWindowHandle); m_hvWindowHandle HTuple(); } CWnd* pCtrl GetDlgItem(IDC_PIC_DISPLAY); CRect rc; pCtrl-GetClientRect(rc); OpenWindow(0, 0, rc.Width(), rc.Height(), pCtrl-GetSafeHwnd(), // 父句柄只认控件不认对话框 visible, , m_hvWindowHandle); SetWindowAttr(flush, false); // 批量绘制时先不刷最后统一 FlushBuffer }flush设成false是个明显的性能开关尤其在一帧里要画几十个轮廓的时候。设完之后记得在绘制结尾调一次FlushBuffer(m_hvWindowHandle)否则画面不动你会以为程序死了。窗口大小变化时要重开或者用SetWindowExtents调整。重开更干脆但必须先把旧窗口关掉这一条我吃过亏——早期版本里每次OnSize都OpenWindow一次而不关旧的跑了一下午任务管理器里 GDI 对象数一路涨到几万最后整个界面变成一片白。4.2 三套坐标图像坐标、控件坐标、屏幕坐标这是新手最容易翻车的地方也是我见过最多“匹配明明对了但框画偏了”的原因。三套坐标的换算方向必须记死Halcon 的Row对应屏幕上的 Y竖直向下Column对应 X水平向右。写代码时row传给了 X 参数结果就是整个框顺时针转了 90 度。控件坐标的原点在控件左上角MFC 的OnLButtonDown给的point是相对对话框的得先ScreenToClient。图像坐标和控件坐标之间隔着一个SetPart缩放比例和偏移都在里面。void CMatchDemoDlg::OnLButtonDown(UINT nFlags, CPoint point) { // MFC 给的 point 是相对对话框客户区的先转到显示控件坐标系 CPoint pt point; m_wndDisplay.ScreenToClient(pt); // 取出当前显示区域对应的图像范围 HTuple hv_Row1, hv_Column1, hv_Row2, hv_Column2; GetPart(m_hvWindowHandle, hv_Row1, hv_Column1, hv_Row2, hv_Column2); CRect rc; m_wndDisplay.GetClientRect(rc); // 控件坐标 - 图像坐标线性映射别在这里加任何经验偏移 double imgRow hv_Row1.D() (hv_Row2.D() - hv_Row1.D()) * pt.y / rc.Height(); double imgCol hv_Column1.D() (hv_Column2.D() - hv_Column1.D()) * pt.x / rc.Width(); // 记下来给 ROI 用 m_dClickRow imgRow; m_dClickCol imgCol; }另一种写法是直接用GetMposition(m_hvWindowHandle, row, col, window)让 Halcon 自己算。它更省事但有个前提调用它的线程必须是开窗的那个线程工作线程里调会返回一堆 0 或者抛异常。所以我在跨线程的场合一律手算宁可多写三行。还有一个隐蔽的坑SetPart传的参数顺序是(Row1, Column1, Row2, Column2)而GenRectangle1的顺序是(Row1, Column1, Row2, Column2)看着一致但很多人顺手把Column2写成了Row2结果显示区域变成一个细长条。这种错误编译器不会报只能靠肉眼发现。4.3 句柄和内存的生命周期别指望 GCHalcon 的HObject内部是引用计数理论上离开作用域就释放。但有两类东西不在此列一是窗口句柄OpenWindow出来的必须CloseWindow二是HTuple里塞的数组如果在一个循环里不停TupleConcat内存会一直涨。我的做法是给图像加一个作用域守卫避免在异常路径上漏掉释放// HObject 的作用域守卫构造时给个空对象析构时强制 ClearObj class HObjScope { public: HObjScope() { m_obj.GenEmptyObj(); } ~HObjScope() { m_obj.ClearObj(); } // 引用计数归零底层内存立即回收 HObject* operator() { return m_obj; } // 支持 ReadImage(scope, ...) 这种写法 HObject Get() { return m_obj; } private: HObjScope(const HObjScope); // 禁止拷贝防止引用计数被意外加一 HObjScope operator(const HObjScope); HObject m_obj; };ClearObj和让它自然析构的区别在于时机自然析构要等栈展开异常路径上如果被catch吞掉又没重新抛出对象可能活到下一轮循环。显式清一下内存曲线会平得多。至于查看内存任务管理器不够用我一般挂个性能计数器看私有字节跑两百帧不回落就是有泄漏。5. 避坑与排查模板匹配在 MFC 里翻车的五种现场5.1 Debug 能跑Release 一启动就崩现象Debug 下匹配正常切到 Release 双击 exe要么闪退要么ReadImage返回空、后面一路空指针。原因九成是运行时库不匹配。工程用/MT静态链接而halconcpp.lib是按/MD编的两套 CRT 同时在进程里堆的分配和释放跨了边界HObject析构时就会踩到别人的堆。解决C/C → 代码生成 → 运行时库Debug 用/MDd、Release 用/MD和 Halcon 保持一致。改完记得全量重编只做增量编译的话旧的 obj 还带着老设置。5.2 换個角度就找不到score 掉到 0.3 以下现象工件摆正的时候 score 0.9 稳如老狗一转到十几度就报未找到或者匹配到一个明显错误的位置。原因三个可能按概率排——AngleExtent没覆盖实际偏转范围AngleStep太粗边界角度上模板和实际错开了半个像素以上MinContrast设太高转到侧面时边缘对比度下降模板点被大量过滤。解决先把AngleStart/AngleExtent放宽到实际范围的两倍做一次验证能稳定找到再收窄再按 3.2 的公式重算AngleStep最后把MinContrast降到建模Contrast的三分之一试试。三步做完还是不行那就是模板本身特征不足得换 ROI 或者加辅助特征。5.3 界面卡死匹配框画在了按钮上现象点开始之后界面卡住几秒后图像出来但画在了对话框左上角盖住了标题栏和按钮。原因算子在 UI 线程里同步跑消息泵被堵住同时OpenWindow的父句柄传成了对话框的m_hWnd而不是 Picture Control 的句柄。解决算子挪到工作线程见 2.3结果通过PostMessage回主线程绘制父句柄改成控件句柄。改父句柄之后要检查OnSize控件尺寸变了窗口得跟着调否则图像会被裁掉一块。5.4 跑一两个小时内存持续上涨现象刚启动时几十兆跑一下午涨到一两个 G最后弹“内存不足”。原因OpenWindow之后没CloseWindow每次重开分辨率或切图都漏一个循环里ReadImage出来的HObject挂在成员变量上没清HTuple在循环里做TupleConcat累积。解决所有窗口操作成对出现写一个ResetWindow()统一管图像用 4.3 的守卫HTuple在循环开头hv_Result HTuple()重置。挂性能计数器观察私有字节跑两百帧不回落就是还有漏。5.5 中文路径的图读不进来现象ReadImage抛异常说文件不存在但资源管理器里明明有或者路径里的中文变成一堆问号。原因MFC 工程默认 UnicodeHTuple接受const char*时按 ANSI 解释宽字符路径直接传进去就是乱码。解决所有传给 Halcon 的字符串都过一遍转换CString strPath _T(D:\\图像\\part_01.png); HTuple hv_File(CT2A(strPath)); // 转成窄字符需要 atlconv.h ReadImage(ho_Image, hv_File);更彻底的办法是工程目录和文件名全用英文但现场拷贝过来的图往往不听话转换这一步别省。6. 交付前的一次压测用五十张图验证稳定性与节拍模板匹配调通不等于能交付。我踩过最疼的一次是现场跑了两小时才开始偶发漏检——光照随时间漂移上午十点以后太阳斜射进车间工件的对比度整体往下掉了三成MinContrast那关就过不去了。从那以后交付前必须跑一轮批量验证。验证分三块缺一不可验证项做法判据覆盖率五十张覆盖全角度范围的真实图逐张跑漏检 0 张误匹配 0 张分数分布统计所有 score 的最小值与均值最小值 MinScore× 1.3重复性同一张固定图连查 100 次Row/Column 标准差 0.05 像素节拍连续跑 500 次取均值单帧耗时 产线节拍的 1/3最后一项留三倍余量不是保守是给现场的老工控机留的——开发机是 i7产线上那台可能是十年前的双核。批量脚本我一般直接写在 demo 里加个隐藏的按钮只在调试模式下显示void CMatchDemoDlg::RunBatchTest() { double minScore 1.0, sumMs 0.0; std::vectordouble vRow; int nOk 0, nNg 0; for (int i 1; i 50; i) { CString strPath; strPath.Format(_T(data\\part_%02d.png), i); // 图按序号命名覆盖全角度 HTuple hv_File(CT2A(strPath)); HObject ho_Image; ReadImage(ho_Image, hv_File); LARGE_INTEGER t0, t1, freq; QueryPerformanceFrequency(freq); QueryPerformanceCounter(t0); // 计时只包住 FindShapeModel HTuple hv_Row, hv_Column, hv_Angle, hv_Score; FindShapeModel(ho_Image, m_hvModelID, HTuple(-15).TupleRad(), HTuple(30).TupleRad(), 0.6, 1, 0.5, least_squares, 0, 0.8, hv_Row, hv_Column, hv_Angle, hv_Score); QueryPerformanceCounter(t1); sumMs (t1.QuadPart - t0.QuadPart) * 1000.0 / freq.QuadPart; if (hv_Score.Length() 0) { nOk; minScore min(minScore, hv_Score[0].D()); vRow.push_back(hv_Row[0].D()); // 重复性用固定图另外跑 } else { nNg; } ho_Image.ClearObj(); } // 结果直接弹个 AfxMessageBox或者写日志别只在调试器里看 CString strMsg; strMsg.Format(_T(通过 %d / 失败 %d / 最低分 %.3f / 平均耗时 %.1f ms), nOk, nNg, minScore, sumMs / 50.0); AfxMessageBox(strMsg); }计时的位置很讲究QueryPerformanceCounter要把FindShapeModel单独包起来别把取图和绘制也算进去否则测出来的是整个流程的耗时优化的时候找不到重点。hv_Score[0]取第一个是因为结果已经按分数降序这里只关心最优的那个。重复性那项要单独做拿同一张图重复查 100 次把Row、Column存进std::vector算标准差。如果标准差跑到 0.3 像素以上说明模板的边缘特征不够锐利或者Greediness设太高导致每次走的搜索路径不同。这个指标比 score 更能反映系统的真实精度因为 score 是相对的重复性是绝对的。还有个小技巧把五十张图的 score 全部打到日志里画个折线看一眼。如果是一条平线说明模板很稳如果中间有个 V 形谷那几张图就是薄弱环节单独拎出来分析光照或者遮挡比现场出了问题再回头查高效得多。我现在的习惯是只要这个 demo 要交给别人用不管对方说得多急批量验证这一轮必须强制走完日志文件跟 exe 一起打包。不是为了证明什么是因为现场那台机器和那张图永远比实验室里多一个你想不到的变量。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑