news 2026/10/1 23:52:59

HALCON图像涂写避坑:窗口叠加层与像素矩阵区分及实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HALCON图像涂写避坑:窗口叠加层与像素矩阵区分及实战

上周又被问了那个老问题:在 HDevelop 里明明用鼠标在图像上圈了个区域、旁边还写了"缺陷"两个字,write_image存出来一看,干干净净,框没了,字也没了。这不是算子写错了,而是把窗口叠加层和图像像素矩阵当成了一回事。HALCON 的图像涂写(paint)之所以容易踩坑,根子就在这条分界线上:dev_display、dev_set_color、disp_text玩的是窗口那层"贴纸",write_image只搬运底下那张"照片"。

这篇东西我就把 HALCON 里跟"图像、区域涂写"相关的算子摊开讲一遍:哪些算子是真改像素、paint_region和overpaint_region该怎么选、Grayval里藏着什么类型陷阱、鼠标交互圈的 ROI 怎么回填、以及"往图上写文字"这件 HALCON 一直没给直通车道的事到底怎么绕。内容偏实战,代码以 HDevelop 语法为主,C#/C++ 调 HALCON 的写法逻辑一样,只是对象句柄的传递方式不同。适合已经能跑通threshold+connection,但一到"标个红框输出给客户看"就卡住的同学。

1. 图像涂写的三条技术路线,选错一条就白干

1.1 窗口图层与像素数据是两套独立系统

先把这张表记住,后面所有坑几乎都是它派生出来的。

你做的动作典型算子作用对象能否被write_image保存
改显示颜色dev_set_color/set_color窗口叠加层否
显示文字disp_text/write_string窗口叠加层否
显示区域轮廓dev_display/set_draw窗口叠加层否
把区域填成灰度paint_region图像像素矩阵是
原地改写像素overpaint_region图像像素矩阵是
生成二值掩膜图region_to_bin新图像是
缩小定义域reduce_domain图像的 domain 标记部分格式会存成裁剪图

这张表的关键信息是第三列。HDevelop 的图形窗口本质上是一块画布,你dev_display(Image)是把像素画上去,然后dev_set_color('red')+dev_display(Region)是在像素上面又刷了一层半透明的红。dev_clear_window一按,这层红就没了,底下的像素一个都没变。很多新手做完演示,按了dump_window_image才发现能存下来,是因为那个算子截的恰恰是"画布",也就是叠加层和底图合成后的结果——这一点在第 4 节会细讲。

所以在动手涂写之前,先问自己一个问题:我要的是"看得见"还是"存得下"?如果只是调试时看得见,dev_set_color一行就够,不要动像素,动了反而污染后续的min_max_gray、intensity这类统计算子。如果这张图要落地成文件、要传给下游、要参与后续算法计算,那必须走paint_region这条真改像素的路。

1.2 reduce_domain 不是涂写,它只是贴了一张"允许通行"的标签

reduce_domain(Image, Region, ImageReduced)这个名字太有迷惑性,很多人以为它把区域外的部分"裁掉"了。它没有。它只是在图像对象上挂了一个 domain,告诉后续算子"只处理这块范围内的像素"。

区别在哪?reduce_domain之后,区域外的像素值原封不动躺在内存里,get_image_size返回的宽高也不变。而paint_region是把区域内的像素值真的改成你指定的灰度。一个改变的是"计算范围",一个改变的是"数据本身"。

我见过一个很典型的事故:有人想给模板匹配做带遮盖的模板,用reduce_domain把干扰区域排除了,然后create_shape_model拿ImageReduced去训练。结果模板里干扰区域还是被算了进去——因为create_shape_model内部对 domain 的处理方式和你想象的不一样,它不是简单地"忽略 domain 外像素"。这种场景正确的做法是先用paint_region把那块干扰涂成一个和背景接近的灰度,抹掉它的梯度,再建模板。

1.3 真正需要动像素的四个高频场景

我梳理了一下自己项目里用到涂写的场合,基本跑不出这四类:

第一类是打靶标记。检测出缺陷后要把缺陷区域标成红色导出,给产线人员或者客户看,这是最刚需的场景,也是paint_region的主场。

第二类是生成掩膜。把 ROI 以外的区域涂成 0,这样后面做min_max_gray、自适应阈值、均值滤波时,边缘不会因为 ROI 外的亮斑产生拖尾。这一招在检测圆形工件边缘时特别管用。

第三类是给算法喂干净的输入。比如一张图上有个固定的反光点,每次都误报,与其在算法里加一堆排除逻辑,不如在预处理阶段用paint_region把它涂成周围背景的中值灰度,后面整条链路的复杂度都能降一档。

第四类是可视化叠加。把深度图或者热力图伪彩之后,再把缺陷轮廓叠上去,生成一张能直接进报告的综合图。

2. paint_region 与 overpaint_region:参数里藏着的三个决定

2.1 Grayval 是元组,长度必须等于通道数

这是最容易被忽略的一条。paint_region(Region, Image, ImageResult, Grayval, Type)里的Grayval不是单值,而是一个元组,长度必须等于Image的通道数。

单通道灰度图,写一个数:paint_region(Defect, Image, ImageMarked, 255, 'fill')。

三通道彩色图,写三个数:paint_region(Defect, Image, ImageMarked, [255, 0, 0], 'fill'),得到的是纯红。这里顺序是 R、G、B,不是 B、G、R,写反了会得到蓝色,别问我怎么知道的。

如果你只给了[255]却喂了一张三通道图,HALCON 不会自动帮你补齐,行为是不可预期的。稳妥的做法是先count_channels(Image, Channels)判断一下,再决定 Grayval 的写法。

还有一个隐藏维度:Grayval的长度是通道数,不是你想要的"颜色数"。有些朋友想涂一个渐变,那得自己算好每个像素值再用gen_image_proto之类的构造,paint_region只支持常量填充。

2.2 Type 的 'fill' 与 'margin' 决定是涂面还是描边

Type只有两个合法取值:

  • 'fill':把区域内部所有像素全部改成Grayval,就是实心涂。
  • 'margin':只把区域的边界像素改成Grayval,内部不动。

想给缺陷画个红框,最直觉的做法是gen_rectangle1生成矩形区域,然后paint_region(..., 'margin')。但实测下来'margin'的线条只有 1 像素宽,在 500 万像素的图上导出成 PNG 再缩放到 PPT 里,基本看不见。

我自己的惯用做法是用两个矩形做差,得到指定粗细的边框环:

gen_rectangle1 (BoxOuter, Row1, Column1, Row2, Column2) gen_rectangle1 (BoxInner, Row1 + 3, Column1 + 3, Row2 - 3, Column2 - 3) difference (BoxOuter, BoxInner, BoxRing) union2 (BoxRing, DefectRegion, ToPaint) paint_region (ToPaint, ImageCopy, ImageMarked, [255, 0, 0], 'fill')

这样边框宽度由3这个偏移量精确控制,想要多粗就调多大,比跟set_line_width较劲可靠得多。而且因为最后统一用'fill'涂,一次调用搞定,比先涂面再描边省一半时间。

2.3 overpaint_region 原地改写带来的连锁反应

overpaint_region(Image, Region, Grayval, Type)和paint_region的关系,类似"就地修改"和"返回新对象"。前者直接把传进来的Image改掉,后者生成一张ImageResult。

那要不要为了省内存一律用overpaint_region?我的经验是分情况:

  • 如果这张图后面还要跟原图比对(比如算差异图),必须用paint_region,先把原图copy_image一份留着。
  • 如果从头到尾就是"预处理一下然后扔掉",overpaint_region更省事,也少一次大图的内存分配。

但overpaint_region有个非常隐蔽的副作用:HALCON 的图像对象是写时复制(copy-on-write)的,如果你有另一个变量还指向同一张底层数据,改写之后那个变量看到的内容也会变。曾经有个项目里我把图存进一个数组做批处理,中间用overpaint_region做了预处理,结果最后汇总输出时发现前面几帧的原始数据全被改花了。排查了两个小时才定位到这一行。所以但凡图像对象存在多个引用,老老实实用paint_region生成新对象。

还有一个坑是overpaint_region对 domain 的处理。它只改 domain 内的像素,region 里超出 domain 的部分会被静默忽略,不报错、不警告。等你发现"涂了半天有一块没涂上",回头查才发现是前面某一步reduce_domain把 domain 缩了。

2.4 灰度值必须匹配图像类型,否则涂了等于没涂

这张表建议贴在显示器边上。

图像类型合法灰度范围涂 255 会怎样
byte0 ~ 255正常,最亮白
uint20 ~ 65535只是偏暗的灰,远不到白
int2-32768 ~ 32767正常但容易溢出误解
real一般 0.0 ~ 1.0严重超范围,显示被截断成纯白
direction角度值语义被破坏,后续方向类算子全乱

最常见的翻车是real图像。你从某个流程拿到一张real类型的深度图或归一化图,顺手paint_region涂了个255,结果那一块全白,和周围完全脱节,而且这个 255 传下去做scale_image时会把整张图的动态范围拉坏。

正确姿势是先确认类型:

get_image_type (Image, Type) if (Type != 'byte') convert_image_type (Image, ImageByte, 'byte') endif

或者按类型给对应的值:real图涂1.0,uint2图涂65535。我一般会在工程里写一个小工具函数,输入图像和区间,自动算出一个"看起来是红色"的灰度值,避免每个项目重新试。

3. 区域侧的准备:从鼠标圈选到批量合并

3.1 交互绘制:set_draw 是窗口级的,别和 dev_set_draw 搞混

做半自动标注工具时,draw_region系列是主力。但很多人第一次用会发现"鼠标画出来的是一片黑,我想要的是轮廓"。原因在set_draw。

set_draw(WindowHandle, Mode)的Mode取'fill'还是'margin',决定的是鼠标拖动过程中视觉反馈的形状,以及draw_region返回的区域是填充的还是描边的。注意,这个状态是挂在WindowHandle上的,每个窗口各管各的。而 HDevelop 里还有一个全局的dev_set_draw,它作用于当前活动窗口,是给dev_display用的。两套东西名字像,作用域不同,混用会出现"我明明设成 margin 了,画出来还是实心"的情况。

常用的一族交互算子,我列个清单方便对照:

算子返回值适合场景
draw_region任意多边形区域不规则缺陷标注
draw_rectangle1两个角点行列坐标正矩形 ROI,最常用
draw_rectangle2中心、角度、半长半宽有旋转的矩形
draw_circle圆心 + 半径圆形工件
draw_ellipse椭圆参数变形目标
draw_polygon多边形区域复杂边界

其中draw_rectangle1我最推荐,因为它返回的是坐标而不是区域,你可以在拿到坐标后做加工——比如向外扩 5 像素留边距,或者坐标对齐到偶数方便后续处理。而draw_region返回的是已经光栅化的区域,想再调整就得走shape_trans或者形态学,灵活性差一些。

3.2 行列顺序:Row 在前,Column 在后

这个坑简单到不好意思写,但每个月都能见到有人在群里问"为什么我的框画在了左上角"。

HALCON 所有跟坐标有关的算子,参数顺序都是Row, Column,也就是先行后列。gen_rectangle1(Region, Row1, Column1, Row2, Column2),其中 Row 是纵坐标(从上往下数),Column 是横坐标(从左往右数)。而get_image_size(Image, Width, Height)返回的却是Width, Height,也就是先列后行。

于是就有了这个经典组合错误:

get_image_size (Image, Width, Height) gen_rectangle1 (BigBox, 0, 0, Width, Height) // 错了,把宽当成了行

如果图是 1920×1080,这一行生成的是一个从 (0,0) 到 (1920,1080) 的矩形,纵向超出了图像 840 像素。在没有 domain 限制的情况下它不报错,只是超出部分被忽略,看起来"框的下半部分没了"。

正确写法是gen_rectangle1(BigBox, 0, 0, Height - 1, Width - 1),注意还要减 1,因为坐标是 0 基的。这个减 1 也是高频疏漏,边界那一行/列经常就这么丢了。

3.3 多个区域先 union 再涂,省时又省心

假设一张图里有 40 个缺陷,逐个paint_region就是 40 次调用,每次都要分配一张新图,内存和耗时都是线性叠加。实测在一张 4096×3000 的图上,40 次单区域paint_region比一次合并区域后涂写慢 6 到 8 倍。

优化思路很朴素:

connection (DefectAll, DefectList) select_shape (DefectList, Selected, 'area', 'and', 50, 99999) union1 (Selected, DefectUnion) copy_image (Image, ImageCopy) paint_region (DefectUnion, ImageCopy, ImageMarked, [255, 0, 0], 'fill')

union1把区域数组压成一个区域,paint_region一次搞定。唯一需要注意的是,如果这些区域颜色各不相同(比如按缺陷等级用红黄绿三色标注),那必须分组涂:按等级select_shape分成三组,每组union1后各涂一次,总共三次调用,依然比 40 次快得多。

这里顺带说一句paint_region内部对区域的扫描方式。它对每个区域做边界框定位,然后逐行扫描填充。所以如果你的区域是那种又大又分散的(比如图像左右两端各一个大块),合并后边界框覆盖了整幅图,扫描效率反而不如分开涂。这种情况建议按位置分簇,别一股脑union1。

4. 往图像里烧文字:没有直通车道,但有两条绕行路

4.1 dump_window_image:把窗口当画布截下来

"HALCON 能在原图上写文字吗"这个问题,热词里也出现了。直接回答:截至目前的算子集里,没有一个write_string_to_image式的直接接口。write_string(WindowHandle, String)写的是窗口,不是图像。

但这不代表做不到。业界最通用的路子是"先在窗口里画好,再把窗口整个截成图像":

* 1. 开一个和图像等大的窗口,保证 1:1 映射 get_image_size (ImageMarked, Width, Height) dev_open_window (0, 0, Width, Height, 'black', WindowHandle) dev_set_part (WindowHandle, 0, 0, Height - 1, Width - 1) * 2. 显示底图 dev_display (ImageMarked) * 3. 设置文字属性 set_font (WindowHandle, 'mono') set_color (WindowHandle, 'white') set_tposition (WindowHandle, 20, 20) write_string (WindowHandle, 'DEFECT 3 PCS') * 4. 截图 dump_window_image (ImageWithText, WindowHandle)

几个关键点必须说清楚:

窗口尺寸必须等于图像尺寸。dev_open_window的宽高直接影响dump_window_image的输出分辨率。窗口开小了,截出来的图就被压缩了,再叠回原图会糊。窗口开大了,输出比原图还大,坐标全对不上。

dev_set_part保证 1:1。如果窗口大小和图像大小一致,但没用dev_set_part把显示范围设成完整图像,HALCON 默认会自适应缩放。一旦缩放,你在窗口里的 (20,20) 映射到图像里就不是 (20,20) 了,位置会飘。

set_tposition的坐标是窗口坐标,不是图像坐标。在 1:1 且无缩放的前提下这两者才等价。这个前提一旦被破坏,文字位置就不可控。

换行用\n。这对应热词里"halcon 换行符号怎么设置"。HDevelop 的字符串字面量支持转义序列,write_string(WindowHandle, 'LINE 1\nLINE 2')就能出两行。要注意的是字号和行距是绑定的,想让行距大一点,得手动分两次set_tposition+write_string,HALCON 没有独立行距参数。

字体。set_font可以传'mono'、'sans'、'serif'这类通用名,也可以传字体文件的完整路径。项目部署时我强烈建议用文件路径固定字体,因为不同机器上字体渲染结果不一样,同一份代码在两台机器上跑的标注图字号会差一截,对不齐会很难看。生产环境我用.ttf路径,随工程一起打包。

如果你只是想量一下文字占多大地方好排版,可以用get_string_extents(WindowHandle, String, Ascent, Descent, TextWidth, TextHeight),拿到宽高之后再决定set_tposition往哪儿放。

4.2 通道对齐:彩色文字到底往哪几个通道写

dump_window_image出来的是三通道 byte 图像,也就是窗口渲染的 RGB 结果。你要把它叠回原来的彩色图上,就得做通道级的合并。

假设底图是ImageMarked(三通道),文字图是ImageWithText(三通道),希望文字盖在底图上:

* 用文字图做前景,按灰度做掩膜 rgb1_to_gray (ImageWithText, TextGray) threshold (TextGray, TextMask, 1, 255) * 逐通道合并 access_channel (ImageMarked, ChR, 1) access_channel (ImageMarked, ChG, 2) access_channel (ImageMarked, ChB, 3) access_channel (ImageWithText, TR, 1) access_channel (ImageWithText, TG, 2) access_channel (ImageWithText, TB, 3) paint_region (TextMask, ChR, ChR2, 255, 'fill') paint_region (TextMask, ChG, ChG2, 255, 'fill') paint_region (TextMask, ChB, ChB2, 255, 'fill') compose3 (ChR2, ChG2, ChB2, ImageFinal)

上面这段为了演示逻辑写得很啰嗦,实际项目里更简洁的方式是直接用dump_window_image的结果作为整图的一部分——如果文字所在区域不重叠重要内容,直接compose3之后按掩膜替换就行。

这里有个细节要留神:dump_window_image的背景色和底图的差异。如果窗口背景是黑的,而底图是白底,那你要么把窗口背景设成和底图一致,要么严格用掩膜只取文字笔画,别把整块背景一起盖上去。我吃过这个亏,图上多了一个大黑方块,排查半天才发现是背景色。

4.3 另一条路:把字形拆成区域,可控但费工

如果项目要求文字必须是纯二值、无抗锯齿、可精确定位到像素(比如做样本生成、做二值化对比测试),上面那条窗口截图的路子就不合适了,因为抗锯齿会引入中间灰度。

这时候只能自己造字库区域。思路是把每个字符预先生成一张小图,threshold出区域后存成region文件,用的时候按字符查表,gen_region出来平移到目标位置,最后union1+paint_region。

这条路我做过一次,只覆盖了 0-9 和 A-Z 加几个符号,工作量不小。它的优势是完全确定性的:给定输入,输出像素完全一致,跨机器、跨版本都稳。如果你做的是自动化测试或者需要严格复现的样本生成,这条路的稳定性值得那点工作量。

5. 几个真实踩过的坑与排查链路

5.1 涂了却看不出来:从类型查到 domain

这个现象我归纳成一套固定排查顺序,基本三步能定位:

第一步,查类型。get_image_type(Image, Type)看是不是real。是real而你又涂了255,那就是范围问题,改用1.0或者先转byte。

第二步,查 domain。get_domain(Image, Domain),然后area_center(Domain, DomainArea, _, _),和get_image_size算出来的总像素数比一下。如果 domain 面积远小于总像素,说明前面有reduce_domain缩过范围,你涂的区域如果落在 domain 外面,就是白涂。

第三步,查通道。count_channels(Image, Channels),确认Grayval的长度对得上。三通道图给单值,有时候不报错但效果诡异。

还有个小概率情况:涂的颜色和背景太接近。用min_max_gray量一下涂写前后的局部灰度,确认差值够大。做可视化输出时,涂写颜色的选择和底图的对比度要专门调一次,别用默认的红——如果底图本身就是暖色调,红框根本跳不出来。

5.2 循环里涂写导致耗时暴涨的定位过程

遇到过一个批处理任务,单张图处理 200ms,批量跑 500 张用了 8 分钟,算下来单张接近 1 秒。用count_seconds在关键节点打点,发现时间全耗在一个循环里的paint_region上。

那段代码大概是这样:

for Index := 0 to |Defects| - 1 by 1 select_obj (Defects, OneDefect, Index + 1) paint_region (OneDefect, ImageWork, ImageWork, [255, 0, 0], 'fill') endfor

问题有两点。一是每次paint_region都生成一张新的三通道大图,500 个缺陷就是 500 次全图内存分配和拷贝;二是输入输出用了同一个变量名,HALCON 内部要做引用检查,额外开销。

改成先合并再涂之后:

union1 (Defects, AllDefects) paint_region (AllDefects, ImageCopy, ImageMarked, [255, 0, 0], 'fill')

单张图的这部分耗时从 700ms 降到 90ms 左右。这个案例我后来写进了组内的规范:任何在循环体内对大图做paint_region的写法,先想想能不能搬到循环外。

还有一种情况不能合并:需要按缺陷等级涂不同颜色。那就按等级分组,组数远小于缺陷数,收益依然很大。

5.3 涂完再 reduce_domain,输出图被裁了

这个坑发生在导出环节。流程是:reduce_domain限定检测区域 → 检测 →paint_region标注 →write_image导出。导出的 PNG 只有检测区域那一块,周围全没了。

原因是write_image会尊重图像的 domain。你的图像经过reduce_domain之后 domain 已经不是全图了,write_image就按 domain 的边界框输出。

解决办法是导出前调full_domain:

full_domain (ImageMarked, ImageFull) write_image (ImageFull, 'png', 0, 'result.png')

full_domain把 domain 恢复成整幅图范围。注意它不改像素值,只是把 domain 的标记恢复,所以原来 domain 外那些"从未被处理过"的像素会原样出现在输出里——如果那些区域的像素值本来就是原图数据,那没问题;如果图像是gen_image_const造出来的空图,那块就是黑的。

如果不想让 domain 外的内容露出来,可以先paint_region把背景统一涂成白或黑,再full_domain,这样导出的是干净的白底标注图。

另外提一个容易混的算子:crop_domain(Image, ImageCropped),它是真的把图像裁成 domain 的边界框,宽高都变了。什么时候用crop_domain?当你的下一步只需要那块 ROI 的像素、不需要保留原图尺寸时用它,能省内存也能提速。什么时候用full_domain?要保留原图坐标系时用它,因为crop_domain之后所有坐标都变了,你之前记下的缺陷坐标全部失效。

6. 一个完整小案例:缺陷区域标红并输出可交付图片

6.1 流程骨架与逐段代码

把前面所有内容串起来,做一个"检测 + 标注 + 导出"的最小可用流程。假设输入是一张有反光干扰的工件图。

* ---------- 1. 读图与信息采集 ---------- read_image (Image, 'workpiece_01.png') get_image_size (Image, Width, Height) count_channels (Image, Channels) get_image_type (Image, ImageType) * ---------- 2. 预处理:抹掉固定反光区 ---------- * 反光区坐标是工艺固定的,直接涂成背景灰度 gen_rectangle1 (GlareArea, 120, 1680, 260, 1880) if (ImageType == 'byte') BackGray := 128 else BackGray := 0.5 endif copy_image (Image, ImageClean) paint_region (GlareArea, ImageClean, ImageClean, BackGray, 'fill') * ---------- 3. 缺陷检测 ---------- rgb1_to_gray (ImageClean, GrayImage) dyn_threshold (GrayImage, mean_image(GrayImage, GrayMean, 21, 21), \ DefectRaw, 15, 'dark') connection (DefectRaw, DefectList) select_shape (DefectList, DefectSelected, 'area', 'and', 80, 999999) * ---------- 4. 生成标注区域:缺陷面 + 外扩边框 ---------- union1 (DefectSelected, DefectUnion) smallest_rectangle1 (DefectSelected, Row1, Col1, Row2, Col2) gen_rectangle1 (BoxOuter, Row1, Col1, Row2, Col2) gen_rectangle1 (BoxInner, Row1 + 3, Col1 + 3, Row2 - 3, Col2 - 3) difference (BoxOuter, BoxInner, BoxRing) union2 (BoxRing, DefectUnion, ToMark) * ---------- 5. 涂写到彩色副本上 ---------- copy_image (Image, ImageMarked) paint_region (ToMark, ImageMarked, ImageMarked, [255, 0, 0], 'fill') * ---------- 6. 恢复全图 domain 并导出 ---------- full_domain (ImageMarked, ImageOut) write_image (ImageOut, 'png', 0, 'result_marked.png')

这段代码有几个地方值得展开说。

第 2 步的固定反光区处理,坐标是硬编码的。这在单工位设备上完全可接受,因为相机和工件的相对位置是锁死的。判断图像类型来选灰度值这一步不能省,real图涂 128 会直接废掉。

第 4 步的smallest_rectangle1我特意用了它而不是smallest_rectangle2。区别在于smallest_rectangle1返回的是正矩形的四个极值坐标,不管你前面有多少个缺陷,它只输出一组坐标——也就是把所有缺陷的并集框住。如果你要每个缺陷单独一个框,得先select_obj逐个算,再union1合并所有框。这里为了代码简洁用了整体框,实际项目里按需选。

第 5 步的copy_image是关键。因为前面第 2 步已经用paint_region改过ImageClean了,如果标注也涂在ImageClean上,导出的图里反光区会带着灰块,客户看了会问"这块是什么"。保留原图Image作为标注底图,视觉上更干净。

6.2 输出前的三点自检

导出之前,我固定会做三件事,做完基本不会返工。

自检一:确认涂写区域面积占比合理。area_center(ToMark, MarkArea, _, _)拿到面积,除以Width * Height。如果占比超过 30%,要么是检测参数太松把整张图都框进去了,要么是背景被误检,先回头查检测,别急着出图。

自检二:确认导出图的宽高和原图一致。get_image_size(ImageOut, W2, H2)然后和Width, Height比。不一致基本就是 domain 的问题,回第 5.3 节看。

自检三:确认颜色对比。如果底图是暖色调,纯红框会糊在一起,改成纯青或者加白边。这个没有通用公式,我就是导出后打开看一眼,觉得不跳就换色。做可视化这件事,肉眼确认比任何参数都可靠。

顺带说一个我常用的小技巧:如果一张图上要标多个缺陷并且要编号,编号文字用第 4 节的dump_window_image路子加上去,最后整体导出。整个链路的顺序是——先涂写所有区域,再在窗口里显示涂好的图,再叠文字,再dump_window_image,再一次write_image。不要在没涂完的时候就截图,那样文字会被后面的涂写覆盖掉。

实际操作下来,这套"先像素后窗口最后截图"的顺序能省掉大量来回调试的时间。我早年习惯先显示后涂写,结果经常出现文字被红块盖住、编号位置对不上涂写结果的情况,改顺序之后这类问题基本绝迹了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 23:52:08

嵌入式Linux WiFi SDIO -110超时错误分析与实战排查

1. 先搞清楚 -110 是谁递出来的做嵌入式 Linux 的,尤其是做 WiFi 模块适配的,基本都会在某块板子上撞见mmc0: error -110 whilst initialising SDIO card这一行日志。我第一次见到它是在一块国产 SoC 的评估板上,WiFi 模块型号刚换&#xff0…

作者头像 李华
网站建设 2026/10/1 23:52:07

SMB协议调试与故障排查:从端口扫描到共享连接的实践指南

简介:smb.rar压缩包提供了一份超级玛丽(Super Mario Bros)风格的2D游戏源代码,面向希望入门游戏开发或研究经典平台动作游戏实现的程序员,可基于DirectX与GLUT环境编译运行。源码主体使用C语言编写,涵盖游戏…

作者头像 李华
网站建设 2026/10/1 23:51:48

模型优化实战:从量化剪枝到TensorRT部署提速指南

从"训得动"到"用得动",这是每个做深度学习落地的工程师都绕不过去的坎。我最早接触Model-Optimizer这个词,是在一个半夜上线的AI推理服务连续超时的故障现场。模型在训练机上跑得好好的,FPS高得感人,一上生产…

作者头像 李华
网站建设 2026/10/1 23:49:05

马德拉群岛深度游攻略:从行前准备到徒步路线全解析

1. 出发前先把“Madeira”这三个词掰开看 很多人第一次看到“Madeira”会愣一下,念成“玛黛拉”也好、“马德拉”也好,其实都没毛病。它在地图上有好几层含义:横卧在大西洋中的马德拉群岛、同名同姓的强化葡萄酒,还有那套传承了数…

作者头像 李华
网站建设 2026/10/1 23:49:00

冒烟测试完全指南:用例设计、自动化落地与面试考点

冒烟测试这名字,听着像是给机器点根烟,实际上干的是“通电即检验”的活。这几年我在项目里见过太多“测试报告全绿、上线两小时就崩”的诡异场景,后来基本都收敛在一个动作上:版本提测之后,先别急着铺开回归&#xff0…

作者头像 李华