halcon中是怎么实现半导体/Led中的GoldenDie的检测方法的 基于局部可变形模板匹配 variation_model模型

原文作者:aircraft

原文地址:https://www.cnblogs.com/DOMLX/p/18739196

这篇简单介绍一下halcon中的print_check_single_chars.hdev 实例 Perform a typical print quality inspection using variation models for each character也就是打印字母质量的瑕疵检测。

这个实例中运用到的variation_model模型来检测就是半导体/Led中的GoldenDie(构阵带?)的检测方法,通过建立标准晶圆(Die)图形模板,然后实际生产中每颗晶圆(Die)就与标准图形模板比对

得到的差异图形,在对差异图形进行分析(面积,灰度等等)来判断每颗晶圆(Die)的NG、OK。如果你将本篇实例掌握理解了,那么就可以把这个检测方法应用到半导体/Led晶圆检测当中去了。

一.主要算子详解

最重要的算子只有几个

1.create_variation_model ( : : Width, Height, Type, Mode : ModelID)

2.train_variation_model (Images : : ModelID : )

3.prepare_variation_model ( : : ModelID, AbsThreshold, VarThreshold : )

4.clear_train_data_variation_model ( : : ModelID : )

5.compare_variation_model (Image : Region : ModelID : )

6.clear_variation_model ( : : ModelID : )

总结起来顺序就是:创建模型,训练模型,给模型设置参数,释放训练参数,输入图像比较模型得到结果,清除释放模型资源。接下来一个个了解一下模型参数。

1.create_variation_model ( : : Width, Height, Type, Mode : ModelID)详解:

参数详解

1. Width & Height

  • 作用 :定义变异模型的参考区域尺寸(即字符在训练图像中对齐后的目标尺寸)。
  • 关键点
    • 必须与训练图像裁剪后的尺寸一致 。例如,若字符在基准图像中被裁剪为 100×50 像素,则 Width=100, Height=50
    • 直接影响模型对齐精度 :若实际字符尺寸与 Width/Height 不匹配,会导致仿射变换误差。

2. Type

  • 数据类型选项
    • 'byte' (默认值): 8-bit无符号整数,适用于亮度值范围在 [0,255] 的图像。
    • 'uint2': 16-bit无符号整数,支持更高动态范围(如HDR图像)。
    • 'float': 浮点数,用于需要高精度计算的场景。
  • 选择依据
    • 若图像经过归一化(如 [0,1]),需选择 'float'
    • 若使用prepare_variation_model时设置了 VarThreshold,建议用 'uint2' 以避免数值溢出。

3. Mode

有三个参数可选:

模式 说明 适用场景
'standard' 默认模式,基于像素值的方差计算变异模型。 常规场景(字符对比度高、噪声较少)
'robust' 使用鲁棒统计(如中位数)替代方差,减少异常值影响。 训练图像含噪声或离群点
'direct' 跳过方差计算,直接使用原始像素值训练模型。 需要最大保留原始像素信息

关键区别

  1. 'standard' vs 'robust'

    • 'standard':对像素值的分布敏感,易受高对比度噪声干扰。
    • 'robust':通过中位数等稳健统计量,对噪声更鲁棒(适合工业检测场景)。
  2. 'direct'

    • 不进行任何统计预处理,直接将像素值作为特征输入模型。
    • 可能导致模型过拟合原始图像噪声,但保留更多细节。

这里我们一般使用前面两个参数,均值或者中值,均值的计算都是每个图像同位置的灰度数据集来统计的,如果你能确保你传入的训练图像非常优秀都是标准的那么选均值就很好,如果你传入的图像不够完美有好的和次好的存在,那么就可以选中值。这时候中值的参数会给你的程序带来更好的抗噪。

2.train_variation_model (Images : : ModelID : )详解:

参数详解

1. Images

  • 类型RegionImage
  • 作用 :提供对齐后的训练图像(已通过仿射变换调整到参考区域尺寸)。
  • 关键要求
    • 尺寸匹配 :图像尺寸必须与 create_variation_model 中定义的 WidthHeight 一致。
    • 内容要求:图像需包含字符区域的变形样本(如旋转、缩放、平移后的实例)。

2. ModelID

  • 类型:整数
  • 作用:指定要训练的变异模型唯一标识符。
  • 依赖 :必须提前通过 create_variation_model 创建。

关键细节

1. 数据增强机制

  • 网格采样:默认将图像划分为多个区域,每个区域的统计特征作为训练样本。
  • 阈值筛选 :通过 prepare_variation_model 排除噪声和非字符区域。

2. 模型更新策略

  • 增量训练 :每次调用 train_variation_model 会累积新的样本到模型中,无需从头训练。
  • 参数存储 :变异模型的参数(如像素分布直方图)保存在 ModelID 对应的内存中。

总结

  • 核心作用:通过输入的训练图像更新变异模型,使其学习字符在不同变形下的外观变化。
  • 依赖关系 :需配合 create_variation_modelprepare_variation_model 使用。
  • 优化方向:合理设置网格参数和阈值,增加训练图像的多样性以提高模型鲁棒性。

3.prepare_variation_model ( : : ModelID, AbsThreshold, VarThreshold : )详解:

参数含义:

  1. AbsThreshold(20)

    • 作用 :筛选图像块时,像素值的绝对偏差阈值
    • 逻辑 :仅保留像素值在 [局部均值 - AbsThreshold, 局部均值 + VarThreshold] 范围内的区域。
    • 示例 :若某像素的局部均值为 100,则仅保留 80 ≤ 像素值 ≤ 120 的区域(假设 VarThreshold=20)。
  2. VarThreshold(3)

    • 作用 :筛选图像块时,像素值的方差阈值
    • 逻辑 :丢弃方差超过 VarThreshold 的图像区域(避免噪声干扰)。
    • 示例 :若某区域的像素方差为 5,则保留该区域;若方差为 6,则丢弃。

函数功能:

  • 训练数据准备
    从当前图像中提取符合阈值的图像块(Tile),用于训练变异模型。
  • 目的:通过阈值筛选,消除噪声和非字符区域,使模型专注于字符本身的变形特征。

本实例中为什么选择 203

这是经验值,需根据字符对比度和背景噪声水平调整:

  • 字符对比度高(如黑色字符在白色背景上):
    • 可适当增大 AbsThreshold(如30),保留更宽的像素范围。
  • 字符边缘细腻(如细线字体):
    • 需减小 VarThreshold(如1),保留更多局部方差小的区域以捕捉细节。

补充说明:

  • 数据增强:此步骤通过阈值筛选,间接实现了对字符区域的"自适应采样"。
  • 与网格采样的区别 :不同于prepare_variation_model的其他参数版本(如网格划分),此参数组合直接通过像素统计特征筛选训练样本。

4.clear_train_data_variation_model ( : : ModelID : )详解:

核心作用

  • 清空训练数据
    删除与指定变异模型(ModelID)关联的所有训练样本和中间计算结果。
  • 释放内存
    回收模型占用的内存空间,避免长期训练导致内存泄漏。
  • 重置训练状态
    将模型恢复到初始状态(未训练状态),允许重新开始训练。

5.compare_variation_model (Image : Region : ModelID : )详解:

参数详解

1. Image

  • 类型 : Image
  • 作用 : 输入的待比较图像,需与训练变异模型时使用的图像尺寸和通道一致(如灰度图或彩色图)。
  • 关键要求:
    • 必须经过与训练阶段相同的仿射变换和对齐操作。
    • 像素值范围需与模型创建时指定的 Type'byte'/'uint2'/'float')匹配。

2. Region

  • 类型 : Region
  • 作用 : 定义在 Image 中要比较的目标区域。该区域的尺寸必须与变异模型的参考区域(Width×Height严格一致
  • 生成方式:
    • 通过 affine_trans_region 将检测到的字符区域变换到参考区域坐标系。

    • 示例代码:

      cpp 复制代码
      vector_angle_to_rigid(..., HomMat2D) affine_trans_region(RegionDetected, RegionAligned, HomMat2D)

3. ModelID

  • 类型 : integer
  • 作用 : 指定要使用的变异模型唯一标识符(需通过 create_variation_model 创建)。

算子: compare_ext_variation_model(Image : Region : ModelID, Mode : )
该算子是算子compare_variation_model的拓展,其参数 MODE可以控制输出暗或亮缺陷或者都输出。

variation_model模型里还有很多其他算子,当你发现当前的算子参数不够完成你要的项目的时候,可以再去看看其他扩展的算子。

工作原理

  1. 模型匹配

    • 将输入图像的 Region 与变异模型进行特征比对,提取统计特征(如局部均值、方差、纹理能量)。
  2. 差异计算

    • 计算像素值和区域特征的绝对偏差,并生成偏差图 Deviation
    • 根据统计特征的匹配程度,输出相似度分数 Similarity
  3. 置信度评估

    • 基于相似度分数生成二值掩模 Confidence,标识高置信度区域。

6.clear_variation_model ( : : ModelID : )详解:

核心作用

  1. 清除训练数据
    删除与指定变异模型(ModelID)关联的所有训练样本和中间计算结果。
  2. 释放内存
    回收模型占用的内存空间,避免长期训练导致内存泄漏。
  3. 重置训练状态
    将模型恢复到初始状态(未训练状态),允许重新开始训练。

print_check_single_chars.hdev 详解:

这个实例主要就是检测打印字母的质量,首先是将打印出来的字母一个个拆分创建模板,然后传入很多训练的图像也是将其中的字母拆分区域传入,训练完毕后你就得到了多个字母区域训练完成可以后续用来比较模板,然后就是传入待检测的图片,同样将待检测的字母分割好一个个对应传入模板比较,得到差异区域,在根据这个差异区域的灰度,面积等参数来确定这个字母质量为OK/NG。

我将实例代码分成七个段落

1.窗体和图像的一些预处理操作

复制代码
*-----------------------------------------------------------------------
* 1. 初始化与图像预处理
*-----------------------------------------------------------------------
dev_update_off()          // 关闭实时更新显示
read_image (Image, 'pen/pen-01') // 读取基准图像
get_image_size (Image, Width, Height)
dev_close_window()        // 关闭现有窗口
dev_open_window (0, 0, Width, Height, 'black', WindowHandle) // 创建新窗口
set_display_font (WindowHandle, 16, 'mono', 'true', 'false') // 设置显示字体为16号等宽黑体
dev_set_color ('red')    // 设置红色显示
dev_display (Image)      // 显示原始图像

2.字符区域分割与预处理

复制代码
*-----------------------------------------------------------------------
* 2. 字符区域分割与预处理
*-----------------------------------------------------------------------
threshold (Image, Region, 100, 255) // 阈值分割(灰度>100为前景)
fill_up (Region, RegionFillUp)       // 填充孔洞
difference (RegionFillUp, Region, RegionDifference) // 取反得到背景区域
shape_trans (RegionDifference, RegionTrans, 'convex') // 转换为凸包区域
dilation_circle (RegionTrans, RegionDilation, 8.5) // 扩展区域以包含边缘噪声
reduce_domain (Image, RegionDilation, ImageReduced)
threshold (ImageReduced, Region, 0, 180) // 二值化处理
connection (Region, ConnectedRegions) // 连通区域分析
sort_region (ConnectedRegions, SortedRegions, 'character', 'true', 'row') // 按字符顺序排序
dilation_circle (SortedRegions, RegionDilation, 1.5) // 轻微膨胀以稳定区域
smallest_rectangle1 (RegionDilation, Row1, Column1, Row2, Column2) // 计算最小包围矩形
count_obj (RegionDilation, Number) // 统计字符数量

Heights := Row2 - Row1 + 5 // 区域高度(含缓冲)
Widths := Column2 - Column1 + 5 // 区域宽度(含缓冲)

gen_empty_obj (ShapeModels)            // 初始化形状模型容器
gen_empty_obj (VariationModelROIs)    // 初始化变异模型ROI容器
ShapeModelIDs := []                   // 存储形状模型ID列表
VariationModelIDs := []               // 存储变异模型ID列表
RowsRef := []                         // 存储基准行坐标
ColumnsRef := []                       // 存储基准列坐标

for I := 1 to Number by 1
    select_obj (RegionDilation, ObjectSelected, I) // 选择第I个字符区域
    Height := Heights[I - 1]                     // 获取字符高度
    Width := Widths[I - 1]                        // 获取字符宽度
    move_region (ObjectSelected, RegionMoved, -Row1[I - 1], -Column1[I - 1]) // 平移至左上角
    crop_part (Image, ImagePart, Row1[I - 1], Column1[I - 1], Widths[I - 1], Heights[I - 1]) // 裁剪字符区域
    reduce_domain (ImagePart, RegionMoved, ImageReduced) // 减少图像域
    inspect_shape_model (ImageReduced, ModelImages, ModelRegions, 1, [15,15,10]) // 检测形状模型
    gen_contours_skeleton_xld (ModelRegions, ModelContour, 1, 'filter') // 生成轮廓
    area_center (RegionMoved, Area, RowRef, ColumnRef) // 计算质心位置(用于对齐)
    create_shape_model (ImageReduced, 5, rad(-10), rad(20), 'auto', 'none', 'use_polarity', 20, 10, ShapeModelID) // 创建形状模型
    create_variation_model (Width, Height, 'byte', 'standard', VariationModelID) // 创建变异模型
    concat_obj (ShapeModels, ModelContour, ShapeModels) // 合并模型轮廓
    concat_obj (VariationModelROIs, RegionMoved, VariationModelROIs) // 合并ROI区域
    RowsRef := [RowsRef,RowRef]                     // 更新行坐标引用
    ColumnsRef := [ColumnsRef,ColumnRef]               // 更新列坐标引用
    ShapeModelIDs := [ShapeModelIDs,ShapeModelID]     // 存储形状模型ID
    VariationModelIDs := [VariationModelIDs,VariationModelID] // 存储变异模型ID
endfor

这边主要是将一个个字母区域提取出来制作成一个个单独的模板。

3. 形状模型可视化

复制代码
*-----------------------------------------------------------------------
* 3. 形状模型可视化
*-----------------------------------------------------------------------
gen_empty_obj (Models)
for I := 1 to Number by 1
    select_obj (ShapeModels, ModelSelected, I) // 选择第I个模型
    hom_mat2d_identity (HomMat2DIdentity)       // 初始化仿射变换矩阵为单位矩阵
    hom_mat2d_translate (HomMat2DIdentity, Row1[I - 1], Column1[I - 1], HomMat2D) // 平移至原始位置
    affine_trans_contour_xld (ModelSelected, ModelTrans, HomMat2D) // 应用仿射变换
    concat_obj (Models, ModelTrans, Models) // 合并可视化模型
endfor

dev_display (Image)
dev_set_colored (6)      // 设置浅灰色
dev_set_draw ('margin')  // 绘制边框
dev_set_line_width (3)   // 边框宽度3像素
dev_display (Models)     // 显示所有形状模型
dev_set_color ('yellow') // 设置黄色
disp_message (WindowHandle, 'Shape models', 'image', 12, 20, 'yellow', 'false') // 显示标题
disp_message (WindowHandle, '(moved to original pos.)', 'image', 45, 20, 'yellow', 'false') // 显示副标题
disp_continue_message (WindowHandle, 'black', 'true') // 继续显示黑色背景
stop () // 暂停等待用户确认

这边主要是将一个个字母区域通过反射变换到匹配所在的位置,让他能够与字母图对应轮廓显示查看。

4. 变异模型训练

复制代码
*-----------------------------------------------------------------------
* 4. 变异模型训练
*-----------------------------------------------------------------------
for J := 1 to 15 by 1
    read_image (Image, 'pen/pen-' + J$'02d') // 读取训练图像(共15张)
    find_shape_models (Image, ShapeModelIDs, rad(-10), rad(20), 0.5, [1,1,1,1,1], 0.5, 'least_squares', 0, 0.9, Row, Column, Angle, Score, Model) // 检测字符位置
    for K := 0 to |Score| - 1 by 1
        vector_angle_to_rigid (Row[K], Column[K], Angle[K], RowsRef[Model[K]], ColumnsRef[Model[K]], 0, HomMat2D) // 计算位姿矩阵
        affine_trans_image_size (Image, ImageTrans, HomMat2D, 'constant', Widths[Model[K]], Heights[Model[K]]) // 图像对齐到基准尺寸
        select_obj (VariationModelROIs, ROI, Model[K] + 1) // 选择对应ROI区域
        reduce_domain (ImageTrans, ROI, ImageTransReduced) // 减少图像域
        train_variation_model (ImageTransReduced, VariationModelIDs[Model[K]]) // 训练变异模型
    endfor
endfor

这边主要是将传入的训练图形中的字母一个个匹配定位到后,将区域反射变换过去然后将字母裁剪出来传入训练。

5. 变异模型可视化验证

复制代码
*-----------------------------------------------------------------------
* 5. 变异模型可视化验证
*-----------------------------------------------------------------------
get_image_size (Image, Width, Height)
gen_empty_obj (MeanImages)
gen_empty_obj (VarImages)
gen_empty_obj (ROIs)

for I := 1 to Number by 1
    get_variation_model (MeanImage, VarImage, VariationModelIDs[I - 1]) // 获取均值/方差图像
    *AbsThreshold = 20:绝对阈值,用于筛选图像块中的像素值。只有像素值在[Mean - AbsThreshold, Mean + VarThreshold]范围内的像素才会被保留。
    *VarThreshold = 3:方差阈值,控制像素值的允许变化范围,基于局部区域的方差。
    prepare_variation_model (VariationModelIDs[I - 1], 20, 3) // 
    

    select_obj (VariationModelROIs, ROI, I) // 选择第I个ROI区域
    move_region (ROI, ROIMoved, Row1[I - 1], Column1[I - 1]) // 移动至基准位置
    reduce_domain (MeanImage, ROI, MeanImageReduced) // 减少域范围
    reduce_domain (VarImage, ROI, VarImageReduced) // 减少域范围
    concat_obj (MeanImages, MeanImageReduced, MeanImages) // 合并均值图像
    concat_obj (VarImages, VarImageReduced, VarImages) // 合并方差图像
    concat_obj (ROIs, ROIMoved, ROIs) // 合并ROI区域
    clear_train_data_variation_model (VariationModelIDs[I - 1]) // 清空训练数据
endfor

tile_images_offset (MeanImages, MeanImage, Row1, Column1, gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), Width, Height) // 并排显示均值图像
tile_images_offset (VarImages, VarImage, Row1, Column1, gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), Width, Height) // 并排显示方差图像

get_domain (Image, Domain) // 获取图像有效域
dev_set_color ('black')    // 设置黑色背景
dev_set_draw ('fill')     // 填充显示
dev_display (Domain)      // 显示图像域
dev_display (MeanImage)   // 显示均值图像
dev_set_colored (6)      // 设置浅灰色边框
dev_set_draw ('margin')  // 绘制边框
dev_set_line_width (1)   // 边框宽度1像素
dev_display (ROIs)       // 显示ROI区域
dev_set_color ('yellow') // 设置黄色文字
disp_message (WindowHandle, 'Reference images', 'image', 20, 20, 'yellow', 'false') // 显示标题
disp_message (WindowHandle, '(moved to original pos.)', 'image', 45, 20, 'yellow', 'false') // 显示副标题
disp_continue_message (WindowHandle, 'black', 'true') // 继续显示黑色背景
stop ()

dev_set_color ('black')
dev_set_draw ('fill')
dev_display (Domain)
dev_display (VarImage)
dev_set_colored (6)
dev_set_draw ('margin')
dev_set_line_width (1)
dev_display (ROIs)
dev_set_color ('yellow')
disp_message (WindowHandle, 'Variation images', 'image', 20, 20, 'yellow', 'false')
disp_message (WindowHandle, '(moved to original pos.)', 'image', 45, 20, 'yellow', 'false')
disp_continue_message (WindowHandle, 'black', 'true')
stop ()

这边就是整合每个模型里的均值图和方差图拼接出来显示给我们看一下过程。

6. 打印错误检测与分类

复制代码
*-----------------------------------------------------------------------
* 6. 打印错误检测与分类
*-----------------------------------------------------------------------
for J := 1 to 30 by 1
    read_image (Image, 'pen/pen-' + J$'02d') // 读取测试图像
    gen_empty_obj (RegionsErrorTrans)        // 初始化错误区域容器
    find_shape_models (Image, ShapeModelIDs, rad(-10), rad(20), 0.5, [1,1,1,1,1], 0.5, 'least_squares', 0, 0.9, Row, Column, Angle, Score, Model) // 检测字符位置
    NumberFound := |Score|                   // 获取检测结果数量
    for K := 0 to NumberFound - 1 by 1
        vector_angle_to_rigid (Row[K], Column[K], Angle[K], RowsRef[Model[K]], ColumnsRef[Model[K]], 0, HomMat2D) // 计算位姿矩阵
        affine_trans_image_size (Image, ImageTrans, HomMat2D, 'constant', Widths[Model[K]], Heights[Model[K]]) // 图像对齐到基准尺寸
        select_obj (VariationModelROIs, ROI, Model[K] + 1) // 选择对应ROI区域
        reduce_domain (ImageTrans, ROI, ImageTransReduced) // 减少图像域
        compare_variation_model (ImageTransReduced, RegionDiff, VariationModelIDs[Model[K]]) // 比较当前图像与变异模型
        connection (RegionDiff, ConnectedRegions) // 连通区域分析
        select_shape (ConnectedRegions, RegionsError, 'area', 'and', 20, 1000000) // 筛选面积>20且<1e6的区域作为错误
        count_obj (RegionsError, NumError) // 统计错误区域数量
        if (NumError > 0)
            vector_angle_to_rigid (RowsRef[Model[K]], ColumnsRef[Model[K]], 0, Row[K], Column[K], Angle[K], HomMat2D) // 计算反向位姿
            affine_trans_region (RegionsError, RegionErrorTrans, HomMat2D, 'nearest_neighbor') // 变换错误区域至原始坐标系
            concat_obj (RegionsErrorTrans, RegionErrorTrans, RegionsErrorTrans) // 合并错误区域
        endif
    endfor
    dev_clear_window ()                        // 清空显示窗口
    dev_display (Image)                         // 显示原始图像
    dev_set_color ('red')                       // 设置红色警告框
    dev_display (RegionsErrorTrans)              // 显示检测结果
    count_obj (RegionsErrorTrans, NumError)     // 统计错误数量
    if (NumError == 0 and NumberFound == Number)
        disp_message (WindowHandle, 'Clip OK', 'image', 20, 20, 'green', 'false') // 显示绿色OK
    else
        disp_message (WindowHandle, 'Clip not OK', 'image', 20, 20, 'red', 'false') // 显示红色NG
    endif
    if (J < 30)
        disp_continue_message (WindowHandle, 'black', 'true') // 显示继续提示
        stop () // 单步调试暂停
    endif
endfor

这边就是将一个个待检测图片传入进去,然后进行一个个的模板区域比对整合结果,在差异参数允许范围内的话就OK,反之NG,然后把差异区域显示出来查看。

7. 资源清理

复制代码
*-----------------------------------------------------------------------
* 7. 资源清理
*-----------------------------------------------------------------------
for I := 1 to Number by 1
    clear_shape_model (ShapeModelIDs[I - 1]) // 清除形状模型
    clear_variation_model (VariationModelIDs[I - 1]) // 清除变异模型
endfor

到这整个实例程序就运行完毕了,思想就是分割各个模板区域,然后创建出各个区域的可变模型来进行一一比对,得到差异图形区域。说白了就是拿个标准图,其他图跟标准图贴起来比对,得到灰度差异图或者局部形状差异图。

那么字母可以这样分割区域检测,半导体/Led的晶圆的各个灰度,颜色或者形状区域是不是也可以这样做呢?这就是GoldenDie(构阵带?应该这样念把)检测。(注:大部分led的公司的晶圆并不标准,用这个调参都会把你调死,最好配合分区域检测方法使用。半导体的高质量Die倒是可以用,不过在半导体中高端设备检测他们又会使用其他更好的检测方法)

附print_check_single_chars.hdev实例总代码:

复制代码
*-----------------------------------------------------------------------
* 1. 初始化与图像预处理
*-----------------------------------------------------------------------
dev_update_off()          // 关闭实时更新显示
read_image (Image, 'pen/pen-01') // 读取基准图像
get_image_size (Image, Width, Height)
dev_close_window()        // 关闭现有窗口
dev_open_window (0, 0, Width, Height, 'black', WindowHandle) // 创建新窗口
set_display_font (WindowHandle, 16, 'mono', 'true', 'false') // 设置显示字体为16号等宽黑体
dev_set_color ('red')    // 设置红色显示
dev_display (Image)      // 显示原始图像

*-----------------------------------------------------------------------
* 2. 字符区域分割与预处理
*-----------------------------------------------------------------------
threshold (Image, Region, 100, 255) // 阈值分割(灰度>100为前景)
fill_up (Region, RegionFillUp)       // 填充孔洞
difference (RegionFillUp, Region, RegionDifference) // 取反得到背景区域
shape_trans (RegionDifference, RegionTrans, 'convex') // 转换为凸包区域
dilation_circle (RegionTrans, RegionDilation, 8.5) // 扩展区域以包含边缘噪声
reduce_domain (Image, RegionDilation, ImageReduced)
threshold (ImageReduced, Region, 0, 180) // 二值化处理
connection (Region, ConnectedRegions) // 连通区域分析
sort_region (ConnectedRegions, SortedRegions, 'character', 'true', 'row') // 按字符顺序排序
dilation_circle (SortedRegions, RegionDilation, 1.5) // 轻微膨胀以稳定区域
smallest_rectangle1 (RegionDilation, Row1, Column1, Row2, Column2) // 计算最小包围矩形
count_obj (RegionDilation, Number) // 统计字符数量

Heights := Row2 - Row1 + 5 // 区域高度(含缓冲)
Widths := Column2 - Column1 + 5 // 区域宽度(含缓冲)

gen_empty_obj (ShapeModels)            // 初始化形状模型容器
gen_empty_obj (VariationModelROIs)    // 初始化变异模型ROI容器
ShapeModelIDs := []                   // 存储形状模型ID列表
VariationModelIDs := []               // 存储变异模型ID列表
RowsRef := []                         // 存储基准行坐标
ColumnsRef := []                       // 存储基准列坐标

for I := 1 to Number by 1
    select_obj (RegionDilation, ObjectSelected, I) // 选择第I个字符区域
    Height := Heights[I - 1]                     // 获取字符高度
    Width := Widths[I - 1]                        // 获取字符宽度
    move_region (ObjectSelected, RegionMoved, -Row1[I - 1], -Column1[I - 1]) // 平移至左上角
    crop_part (Image, ImagePart, Row1[I - 1], Column1[I - 1], Widths[I - 1], Heights[I - 1]) // 裁剪字符区域
    reduce_domain (ImagePart, RegionMoved, ImageReduced) // 减少图像域
    inspect_shape_model (ImageReduced, ModelImages, ModelRegions, 1, [15,15,10]) // 检测形状模型
    gen_contours_skeleton_xld (ModelRegions, ModelContour, 1, 'filter') // 生成轮廓
    area_center (RegionMoved, Area, RowRef, ColumnRef) // 计算质心位置(用于对齐)
    create_shape_model (ImageReduced, 5, rad(-10), rad(20), 'auto', 'none', 'use_polarity', 20, 10, ShapeModelID) // 创建形状模型
    create_variation_model (Width, Height, 'byte', 'standard', VariationModelID) // 创建变异模型
    concat_obj (ShapeModels, ModelContour, ShapeModels) // 合并模型轮廓
    concat_obj (VariationModelROIs, RegionMoved, VariationModelROIs) // 合并ROI区域
    RowsRef := [RowsRef,RowRef]                     // 更新行坐标引用
    ColumnsRef := [ColumnsRef,ColumnRef]               // 更新列坐标引用
    ShapeModelIDs := [ShapeModelIDs,ShapeModelID]     // 存储形状模型ID
    VariationModelIDs := [VariationModelIDs,VariationModelID] // 存储变异模型ID
endfor

*-----------------------------------------------------------------------
* 3. 形状模型可视化
*-----------------------------------------------------------------------
gen_empty_obj (Models)
for I := 1 to Number by 1
    select_obj (ShapeModels, ModelSelected, I) // 选择第I个模型
    hom_mat2d_identity (HomMat2DIdentity)       // 初始化仿射变换矩阵为单位矩阵
    hom_mat2d_translate (HomMat2DIdentity, Row1[I - 1], Column1[I - 1], HomMat2D) // 平移至原始位置
    affine_trans_contour_xld (ModelSelected, ModelTrans, HomMat2D) // 应用仿射变换
    concat_obj (Models, ModelTrans, Models) // 合并可视化模型
endfor

dev_display (Image)
dev_set_colored (6)      // 设置浅灰色
dev_set_draw ('margin')  // 绘制边框
dev_set_line_width (3)   // 边框宽度3像素
dev_display (Models)     // 显示所有形状模型
dev_set_color ('yellow') // 设置黄色
disp_message (WindowHandle, 'Shape models', 'image', 12, 20, 'yellow', 'false') // 显示标题
disp_message (WindowHandle, '(moved to original pos.)', 'image', 45, 20, 'yellow', 'false') // 显示副标题
disp_continue_message (WindowHandle, 'black', 'true') // 继续显示黑色背景
stop () // 暂停等待用户确认

*-----------------------------------------------------------------------
* 4. 变异模型训练
*-----------------------------------------------------------------------
for J := 1 to 15 by 1
    read_image (Image, 'pen/pen-' + J$'02d') // 读取训练图像(共15张)
    find_shape_models (Image, ShapeModelIDs, rad(-10), rad(20), 0.5, [1,1,1,1,1], 0.5, 'least_squares', 0, 0.9, Row, Column, Angle, Score, Model) // 检测字符位置
    for K := 0 to |Score| - 1 by 1
        vector_angle_to_rigid (Row[K], Column[K], Angle[K], RowsRef[Model[K]], ColumnsRef[Model[K]], 0, HomMat2D) // 计算位姿矩阵
        affine_trans_image_size (Image, ImageTrans, HomMat2D, 'constant', Widths[Model[K]], Heights[Model[K]]) // 图像对齐到基准尺寸
        select_obj (VariationModelROIs, ROI, Model[K] + 1) // 选择对应ROI区域
        reduce_domain (ImageTrans, ROI, ImageTransReduced) // 减少图像域
        train_variation_model (ImageTransReduced, VariationModelIDs[Model[K]]) // 训练变异模型
    endfor
endfor

*-----------------------------------------------------------------------
* 5. 变异模型可视化验证
*-----------------------------------------------------------------------
get_image_size (Image, Width, Height)
gen_empty_obj (MeanImages)
gen_empty_obj (VarImages)
gen_empty_obj (ROIs)

for I := 1 to Number by 1
    get_variation_model (MeanImage, VarImage, VariationModelIDs[I - 1]) // 获取均值/方差图像
    *AbsThreshold = 20:绝对阈值,用于筛选图像块中的像素值。只有像素值在[Mean - AbsThreshold, Mean + VarThreshold]范围内的像素才会被保留。
    *VarThreshold = 3:方差阈值,控制像素值的允许变化范围,基于局部区域的方差。
    prepare_variation_model (VariationModelIDs[I - 1], 20, 3) // 
    

    select_obj (VariationModelROIs, ROI, I) // 选择第I个ROI区域
    move_region (ROI, ROIMoved, Row1[I - 1], Column1[I - 1]) // 移动至基准位置
    reduce_domain (MeanImage, ROI, MeanImageReduced) // 减少域范围
    reduce_domain (VarImage, ROI, VarImageReduced) // 减少域范围
    concat_obj (MeanImages, MeanImageReduced, MeanImages) // 合并均值图像
    concat_obj (VarImages, VarImageReduced, VarImages) // 合并方差图像
    concat_obj (ROIs, ROIMoved, ROIs) // 合并ROI区域
    clear_train_data_variation_model (VariationModelIDs[I - 1]) // 清空训练数据
endfor

tile_images_offset (MeanImages, MeanImage, Row1, Column1, gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), Width, Height) // 并排显示均值图像
tile_images_offset (VarImages, VarImage, Row1, Column1, gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), gen_tuple_const(Number,-1), Width, Height) // 并排显示方差图像

get_domain (Image, Domain) // 获取图像有效域
dev_set_color ('black')    // 设置黑色背景
dev_set_draw ('fill')     // 填充显示
dev_display (Domain)      // 显示图像域
dev_display (MeanImage)   // 显示均值图像
dev_set_colored (6)      // 设置浅灰色边框
dev_set_draw ('margin')  // 绘制边框
dev_set_line_width (1)   // 边框宽度1像素
dev_display (ROIs)       // 显示ROI区域
dev_set_color ('yellow') // 设置黄色文字
disp_message (WindowHandle, 'Reference images', 'image', 20, 20, 'yellow', 'false') // 显示标题
disp_message (WindowHandle, '(moved to original pos.)', 'image', 45, 20, 'yellow', 'false') // 显示副标题
disp_continue_message (WindowHandle, 'black', 'true') // 继续显示黑色背景
stop ()

dev_set_color ('black')
dev_set_draw ('fill')
dev_display (Domain)
dev_display (VarImage)
dev_set_colored (6)
dev_set_draw ('margin')
dev_set_line_width (1)
dev_display (ROIs)
dev_set_color ('yellow')
disp_message (WindowHandle, 'Variation images', 'image', 20, 20, 'yellow', 'false')
disp_message (WindowHandle, '(moved to original pos.)', 'image', 45, 20, 'yellow', 'false')
disp_continue_message (WindowHandle, 'black', 'true')
stop ()

*-----------------------------------------------------------------------
* 6. 打印错误检测与分类
*-----------------------------------------------------------------------
for J := 1 to 30 by 1
    read_image (Image, 'pen/pen-' + J$'02d') // 读取测试图像
    gen_empty_obj (RegionsErrorTrans)        // 初始化错误区域容器
    find_shape_models (Image, ShapeModelIDs, rad(-10), rad(20), 0.5, [1,1,1,1,1], 0.5, 'least_squares', 0, 0.9, Row, Column, Angle, Score, Model) // 检测字符位置
    NumberFound := |Score|                   // 获取检测结果数量
    for K := 0 to NumberFound - 1 by 1
        vector_angle_to_rigid (Row[K], Column[K], Angle[K], RowsRef[Model[K]], ColumnsRef[Model[K]], 0, HomMat2D) // 计算位姿矩阵
        affine_trans_image_size (Image, ImageTrans, HomMat2D, 'constant', Widths[Model[K]], Heights[Model[K]]) // 图像对齐到基准尺寸
        select_obj (VariationModelROIs, ROI, Model[K] + 1) // 选择对应ROI区域
        reduce_domain (ImageTrans, ROI, ImageTransReduced) // 减少图像域
        compare_variation_model (ImageTransReduced, RegionDiff, VariationModelIDs[Model[K]]) // 比较当前图像与变异模型
        connection (RegionDiff, ConnectedRegions) // 连通区域分析
        select_shape (ConnectedRegions, RegionsError, 'area', 'and', 20, 1000000) // 筛选面积>20且<1e6的区域作为错误
        count_obj (RegionsError, NumError) // 统计错误区域数量
        if (NumError > 0)
            vector_angle_to_rigid (RowsRef[Model[K]], ColumnsRef[Model[K]], 0, Row[K], Column[K], Angle[K], HomMat2D) // 计算反向位姿
            affine_trans_region (RegionsError, RegionErrorTrans, HomMat2D, 'nearest_neighbor') // 变换错误区域至原始坐标系
            concat_obj (RegionsErrorTrans, RegionErrorTrans, RegionsErrorTrans) // 合并错误区域
        endif
    endfor
    dev_clear_window ()                        // 清空显示窗口
    dev_display (Image)                         // 显示原始图像
    dev_set_color ('red')                       // 设置红色警告框
    dev_display (RegionsErrorTrans)              // 显示检测结果
    count_obj (RegionsErrorTrans, NumError)     // 统计错误数量
    if (NumError == 0 and NumberFound == Number)
        disp_message (WindowHandle, 'Clip OK', 'image', 20, 20, 'green', 'false') // 显示绿色OK
    else
        disp_message (WindowHandle, 'Clip not OK', 'image', 20, 20, 'red', 'false') // 显示红色NG
    endif
    if (J < 30)
        disp_continue_message (WindowHandle, 'black', 'true') // 显示继续提示
        stop () // 单步调试暂停
    endif
endfor

*-----------------------------------------------------------------------
* 7. 资源清理
*-----------------------------------------------------------------------
for I := 1 to Number by 1
    clear_shape_model (ShapeModelIDs[I - 1]) // 清除形状模型
    clear_variation_model (VariationModelIDs[I - 1]) // 清除变异模型
endfor