4. 图像预处理与增强的工程化封装

在工业视觉项目里,预处理往往占据整条流水线一半以上的代码量,却最容易被写成“一次性脚本”:换一个相机、换一种光照,就要把灰度变换、滤波、形态学、ROI 全部重写一遍。工程化封装的目标,是把这些步骤从“散落在主流程里的算子调用”变成“有明确输入输出契约、可参数化、可复用、可单独验证”的预处理算子。本节围绕 HALCON 的实际算子,讲清楚四类预处理各自该封装什么,以及如何把它们组织成一个可复用的预处理模块。

一、四类预处理在工程中的职责边界

灰度变换解决的是“对比度与亮度是否落在可用区间”。HALCON 中常用的有 scale_image(线性缩放与偏移)、scale_image_max(按极值拉伸到 0~255)、emphasize(高频增强)、equ_histo_image(直方图均衡)。工程封装时不应只暴露一个“增强”按钮,而应把 Mult、Add 两个参数显式暴露,因为线性变换的物理含义清晰:输出 = 输入 × Mult + Add,便于在不同光照批次间做标定式调整。

滤波解决的是“噪声与细节的取舍”。gauss_filter 适合高斯平滑,median_image 对椒盐噪声更稳健,mean_image 计算快但会模糊边缘,sobel_amp、edges_image 用于提取边缘。封装的关键是固定滤波类型与核尺寸的对应关系,避免调用方随意传入不匹配的 MaskType 与 Radius。

形态学解决的是“形状层面的修正”。opening_circle、closing_circle、opening_rectangle1、closing_rectangle1 用于去毛刺、填小孔;erosion_circle、dilation_circle 用于分离粘连或加粗目标。封装时应把结构元形状、半径、迭代次数作为参数,并明确“先开后闭”还是“先闭后开”的顺序,因为顺序不同结果差异明显。

ROI 管理解决的是“只处理该处理的地方”。HALCON 中 ROI 可以是矩形 gen_rectangle1、圆 gen_circle、任意区域 gen_region_polygon,也可以是 reduce_domain 得到的图像域。工程封装的核心不是“画 ROI”,而是把 ROI 的生成、裁剪、坐标还原三件事绑定在一起:在 ROI 内处理得到的区域,必须能通过 union 或坐标偏移还原到原图坐标系,否则后续测量会整体偏移。

预处理算子封装:输入契约 → 四类处理 → 输出契约 输入 Image ROI 参数 灰度变换 scale_image Mult / Add 滤波 gauss / median MaskType / Radius 形态学 opening / closing Radius / Iterations 输出 ImageOut RegionOut ROI 管理:坐标一致性是封装底线 原图坐标系 gen_rectangle1 Row1,Col1,Row2,Col2 reduce_domain 裁剪出 ROI 图像 域外像素不参与 ROI 内处理 滤波 / 形态学 得到局部 Region 坐标还原 union + 偏移 回到原图坐标 可复用算子封装:参数化 + 默认值 + 校验 算子接口 preprocess_image (Image, Params, ImageOut) 参数结构 Mult, Add, FilterType Radius, MorphOrder 默认值策略 Mult=1.0, Add=0 Radius=1.5, 先开后闭 参数校验 Radius > 0 ROI 在图像内

二、把预处理封装成可复用算子的具体做法

HALCON 本身支持用 HDevelop 过程(procedure)或外部过程库来封装。一个可复用的预处理算子,接口应尽量窄:输入一张图像、一个参数元组,输出处理后的图像和可选区域。下面给出一个可直接参考的过程骨架,参数用元组传递,避免为每个参数单独开形参导致接口膨胀。

* preprocess_image.hdvp
* 输入: Image, Params
* Params = [Mult, Add, FilterType, Radius, MorphOrder, MorphRadius]
* FilterType: 'gauss' | 'median' | 'none'
* MorphOrder: 'open_close' | 'close_open' | 'none'

procedure preprocess_image (Image, Params : ImageOut : : )
  Mult        := Params[0]
  Add         := Params[1]
  FilterType  := Params[2]
  Radius      := Params[3]
  MorphOrder  := Params[4]
  MorphRadius := Params[5]

  * 1. 灰度变换
  scale_image (Image, ImageScaled, Mult, Add)

  * 2. 滤波
  if (FilterType == 'gauss')
    gauss_filter (ImageScaled, ImageFiltered, Radius)
  elseif (FilterType == 'median')
    median_image (ImageScaled, ImageFiltered, 'circle', Radius, 'mirrored')
  else
    copy_image (ImageScaled, ImageFiltered)
  endif

  * 3. 形态学(在图像域上做,也可改为在区域域上做)
  if (MorphOrder == 'open_close')
    opening_circle (ImageFiltered, ImageOpen, MorphRadius)
    closing_circle (ImageOpen, ImageOut, MorphRadius)
  elseif (MorphOrder == 'close_open')
    closing_circle (ImageFiltered, ImageClose, MorphRadius)
    opening_circle (ImageClose, ImageOut, MorphRadius)
  else
    copy_image (ImageFiltered, ImageOut)
  endif
endprocedure

这段骨架体现了三个工程要点。第一,参数集中在一个元组里,调用方只需维护一份参数配置,便于把不同产品的参数存成配置文件或数据库记录。第二,滤波与形态学都做了“类型分支 + 默认直通”,当某一步不需要时不会报错,也不会引入额外计算。第三,所有中间图像都显式命名,便于在调试时单独 dev_display 观察每一步的效果,而不是只看到最终结果。

三、ROI 管理的封装:从“画框”到“可复用区域模板”

ROI 在工程中通常有两种用法。一种是固定 ROI:相机与工件相对位置稳定,ROI 直接写成常量矩形,封装时把 Row1, Col1, Row2, Col2 作为参数传入,内部用 gen_rectangle1 生成区域,再用 reduce_domain 裁剪。另一种是动态 ROI:先通过模板匹配或 Blob 找到工件,再根据匹配位姿生成跟随的 ROI,这时封装的重点是“位姿 → ROI 区域”的转换,通常用 affine_trans_region 把标准 ROI 变换到当前位姿。

无论哪种用法,封装时都要保证 ROI 的坐标参照系一致。如果 ROI 是在原图上定义的,那么 ROI 内处理得到的区域必须通过 union1 或坐标偏移还原到原图坐标系,否则后续 measure_pos、fit_circle_contour_xld 得到的尺寸会整体偏移一个 ROI 左上角坐标。建议在封装内部统一约定:所有输出区域一律使用原图坐标系,ROI 偏移只在内部临时使用。

调试建议:把预处理算子的每一步中间结果都保留为可选输出(例如通过一个 Debug 布尔参数控制是否返回中间图像),这样在产线换型时能快速定位是灰度变换、滤波还是形态学导致的效果变化,而不必重新跑整条流程。

四、参数默认值与校验:让算子“开箱可用”

可复用算子的默认值应来自典型工业场景的经验区间,而不是随意填 0。例如 Mult 默认 1.0、Add 默认 0,表示不做灰度变换;FilterType 默认 'gauss'、Radius 默认 1.5,对应轻微平滑;MorphOrder 默认 'open_close'、MorphRadius 默认 2.0,对应先去除小毛刺再填补小孔。这些默认值应写进算子文档,并在参数校验中检查 Radius > 0、MorphRadius >= 1、ROI 是否落在图像范围内。

参数校验不是形式主义。工业现场最常见的故障之一,就是调用方传入了一个超出图像范围的 ROI,导致 reduce_domain 返回空区域,后续所有算子都在空区域上运行,最终结果为空却不报错。封装时应在 ROI 生成后立即用 area_center 检查区域面积是否大于 0,若为 0 则抛出明确异常,把问题挡在预处理阶段。

五、一个可参考的预处理参数配置表

下表给出一个示例配置,用于某类金属件表面检测场景:光照偏暗、存在细小划痕与少量反光点。表中参数为教学示例,实际项目需根据图像直方图和试跑结果调整。

参数 示例值 作用 调整依据
Mult1.6提升对比度直方图集中在 60~140,需拉伸
Add-20压低背景亮度背景偏亮,压暗后目标更突出
FilterType'median'抑制反光点存在孤立亮点,中值更稳健
Radius2.0滤波核半径划痕宽度约 3~5 像素,不宜过大
MorphOrder'open_close'去毛刺后填孔先去除小凸起,再闭合小缺口
MorphRadius2.0结构元半径与划痕尺度匹配,避免抹掉缺陷

封装完成后,验证方式不是“看起来正常”,而是用同一组参数在至少三张不同光照条件下的图像上运行,检查输出图像的灰度均值、目标区域面积、边缘位置是否稳定。若某张图的目标面积波动超过 10%,说明灰度变换或滤波参数对光照过于敏感,应回到参数标定环节,而不是在后续测量环节打补丁。预处理封装的价值,正在于把这种波动尽早暴露在流水线前端。