各个模板匹配方法的原理,使用方法以及如何选择。
1. 模板匹配方法概述
对于halcon几种模板匹配方法应该如何选择呢?
模板匹配方法 | 选择指南 |
---|---|
相关性匹配 | 适用于纹理复杂,存在失焦导致的模糊、轮廓消失的情况 |
形状匹配 | 适用于轮廓复杂的图像,使用单个模板roi就可以满足定位检测需求 |
组件匹配 | 应用图像存在复杂的轮廓结构,且使用单个roi无法满足匹配定位需求 |
局部可变形匹配 | 待匹配对象与模板对象存在轻微的变形 |
透视变形的匹配 | 待匹配对象存在透视变换 |
基于描述符的匹配 | 记住特征描述符检测特征点建立模板,所以要有明显的特征点,比如角点 |
- 基于相关性的匹配方法
基本原理:基于相关性的匹配是基于灰度值的。这种方法使用归一化的互相关来评估模型和搜索图像之间的对应关系。它非常快,可以补偿照明中的加法和乘法变化。 - 基于形状的模板匹配方法
基本原理:基于形状的匹配不使用像素及其邻域的灰度值作为模板,而是通过轮廓的形状来描述模型。还可以通过关于相邻边缘的进一步约束来扩展该模型。 - 基于组件的匹配方法
基本原理:该方法是基于形状匹配方法的扩展,区别在于组件匹配同时包含多个模板的匹配并且允许这些模板之间存在位移和旋转变换,但是在创建该模板时,需要制定可能存在的位置和旋转关系,特别的是该模板匹配方法不支持组件缩放匹配。 - 局部可变性匹配方法
基本原理:基于形状匹配的改进方法,主要用于匹配与模板之间存在变形的对象。 - 透视变形的匹配方法
基本原理:基于形状匹配的改进,匹配透视变换的对象。 - 基于描述符的匹配方法
基本原理:基于图像的特征描述符提取图像特征建立模板,可以实现与透视变形匹配的功能。
2. 创建模板的方法总结
-
单ROI直接创建法(适用于模板图像在原始图像上易于提取,且包含较少噪声)
-
多ROI通过union2和difference创建法(适用于模板本身需要多个不关联的组件,或需要在一个roi内屏蔽一部分区域)
-
基于合成模板图像创建法(适用于上述两种方法都不易创建稳定模板,且结构简单的模板,比如圆或矩形,当然也可所以使用分割等方法提取轮廓再创建复杂的合成模板)
创建合成模板的方法流程:
// step1: 创建XLD轮廓,注意创建的合成区域应略大于真实的模板区域 RadiusCircle := 43 SizeSynthImage := 2 * RadiusCircle + 10 gen_ellipse_contour_xld (Circle, SizeSynthImage / 2, SizeSynthImage / 2, 0, RadiusCircle, RadiusCircle, 0, 6.28318, 'positive', 1.5) // step2: 创建一个空图,并将生成的轮廓插入到图像中 gen_image_const (EmptyImage, 'byte', SizeSynthImage, SizeSynthImage) paint_xld (Circle, EmptyImage, SyntheticModelImage, 128) // step3:根据合成图像,创建模板 create_scaled_shape_model (SyntheticModelImage, 'auto', 0, 0, 0.01, 0.8, 1.2, 'auto', 'none', 'use_polarity', 30, 10, ModelID)
创建结果图像如下所示:
-
基于XLD轮廓直接创建模板(适用于形状匹配、局部和透视变形匹配)
下面的create_shape_model_xld可以替换为对应方法的创建算子gen_circle_contour_xld (ContCircle, 300, 300, MeanRadius, 0, 6.28318, 'positive', 1) create_shape_model_xld (ContCircle, 'auto', 0, 0, 'auto', 'auto', 'ignore_local_polarity', 10, ModelID)
-
可以从DFX文件中读取(读取轮廓后创建合成模板图像)
3. 模型的重复使用
3.1 保存模板以及保存创建模板时使用的domain图像。
模型被创建后,通常可以使用 write_shape_model 算子将创建的模板的轮廓、关键点、以及创建模板时使用的参数。当存储创建的模板时,并不会保存创建模板时所用的domain图像,这时可以使用 write_image 算子保存创建模板时使用的domain图像。
3.2 读取保存的模板,重新使用
// step1: 创建模板
create_scaled_shape_model (ImageROI, 'auto', -rad(30), rad(60), 'auto', 0.6, 1.4, 'auto', 'none', 'use_polarity', 60, 10, ModelID)
// step2: 保存模板以及创建模板时所用的domain图像
write_shape_model (ModelID, ModelFile)
ModelRegionFile := 'model_region_nut.png'
write_image (ImageROI, 'png', 0, ModelRegionFile)
// step3:读取模板以及相关参数
read_shape_model (ModelFile, ReusedModelID)
get_shape_model_contours (ReusedShapeModel, ReusedModelID, 1) // 获取模板轮廓
get_shape_model_origin (ReusedModelID, ReusedRefPointRow, ReusedRefPointCol) // 返回模板的参考点
get_shape_model_params (ReusedModelID, NumLevels, AngleStart, AngleExtent, AngleStep, ScaleMin,ScaleMax, ScaleStep, Metric, MinContrast) // 获取创建模板时的参数,用于传递给find_scaled_shape_model
read_image (ImageModelRegion, 'model_region_nut.png')
get_domain (ImageModelRegion, DomainModelRegion)
// 使用读取的模板
find_scaled_shape_model (SearchImage, ReusedModelID, AngleStart, AngleExtent, ScaleMin, ScaleMax, 0.65, 0, 0, 'least_squares', 0, 0.8, RowCheck, ColumnCheck, AngleCheck, ScaleCheck, Score)
for i := 0 to |Score| - 1 by 1
get_hom_mat2d_from_matching_result (RowCheck[i], ColumnCheck[i], AngleCheck[i], ScaleCheck[i], ScaleCheck[i], MoveAndScalingOfObject)
affine_trans_contour_xld (ReusedShapeModel, ModelAtNewPosition, MoveAndScalingOfObject)
dev_display (ModelAtNewPosition)
endfor
4. 模板匹配加速处理
- 限制模板搜索的空间(设置搜索图像区域、搜索比例、搜索时的旋转度数、搜索的移动步长等)
- 使用图像金字塔加速(设置下采样的层数)
模板匹配的基本流程和具体创建案例(更多案例参考官方代码)
5.1 基于shape模板匹配的基本使用流程
- 选择模板区域
- 创建模板
- 执行模板匹配
- 显示模板就匹配结果
* step1: slider的检测,并创建模板
set_system ('border_shape_models', 'false')
* Matching 02: Obtain the model image
*read_image (Image1, 'C:/Users/admin/Desktop/mei_2/ABS image/OKImage 20230321 103432645.bmp')
* Matching 02: Build the ROI from basic regions
read_image (Image1, 'C:/Users/admin/Desktop/qua/OKImage 20230426 140913289.bmp')
* read_image (Image1, 'C:/Users/admin/Desktop/32potting/NGImage 20230322 094745455.bmp')
dev_open_window_fit_image (Image1, 0, 0, -1, -1, WindowHandle)
dev_display (Image1)
* draw_rectangle1 (WindowHandle, Row11, Column11, Row22, Column22)
*up gen_rectangle1 (ModelRegion, 420.7, 879.1, 778.3, 1505.5)
* down 466.3 857.5 823.9 1479.1
* 创建左圆的检测中心点
* draw_rectangle1 (WindowHandle, Row_c11, Column_c11, Row_c12, Column_c12)
* 447.1 456.7 816.7 691.9
gen_rectangle1 (Rectangle_LC, 447.1, 456.7, 816.7, 691.9)
*完成了左圆心选择操作
* 创建右侧两个分割区域的模板匹配
*draw_rectangle1 (WindowHandle, Rowru11, Columnru11, Rowru2, Columnru2)
* 312.7 1143.1 466.3 1323.1
gen_rectangle1 (ModelRegionru, 312.7, 1143.1, 466.3, 1323.1)
* Matching 02: Reduce the model template
reduce_domain (Image1, ModelRegionru, TemplateImageru)
* Matching 02: Create the shape model
*draw_rectangle1 (WindowHandle, RowrD11, ColumnrD11, RowrD2, ColumnrD2)
*835.9, 1135.9,970.3,1327.9
gen_rectangle1 (ModelRegionrD, 835.9, 1135.9,970.3,1327.9)
* Matching 02: Reduce the model template
reduce_domain (Image1, ModelRegionrD, TemplateImagerD)
create_shape_model (TemplateImageru, 6, rad(0), rad(360), rad(0.3146), ['point_reduction_high','pregeneration'], 'use_polarity', [5,13,21], 4, ModelID_RU)
* Matching 02: Create the shape model
create_shape_model (TemplateImagerD, 6, rad(0), rad(360), rad(0.3146), ['point_reduction_high','pregeneration'], 'use_polarity', [5,13,21], 4, ModelID_RD)
* Matching 02: Get the model contour for transforming it later into the image
*完成右侧两个圆的检测
*select_obj (ConnectedRegions, ObjectSelected, 1)
dev_set_draw ('margin')
gen_rectangle1 (ModelRegion, 466.3, 857.5, 823.9, 1479.1)
* Matching 02: Reduce the model template
reduce_domain (Image1, ModelRegion, TemplateImage)
* Matching 02: Create the shape model
create_shape_model (TemplateImage, 6, rad(0), rad(360), rad(0.3146), ['point_reduction_high','pregeneration'], 'use_polarity', [5,13,21], 4, ModelID)
* Matching 02: Get the model contour for transforming it later into the image
gen_rectangle1 (Rectangle15, 0,0, 318, 619)
open_file ('C:/Users/admin/Desktop/new11-20/20-9/result.txt', 'append', FileHandle)
* step2: 进行相关检测
list_files ('C:/Users/admin/Desktop/11-20/20-9', ['files','follow_links'], ImageFiles)
tuple_regexp_select (ImageFiles, ['\\.(tif|tiff|gif|bmp|jpg|jpeg|jp2|png|pcx|pgm|ppm|pbm|xwd|ima|hobj)$','ignore_case'], ImageFiles)
for Index1 := 0 to |ImageFiles| - 1 by 1
* Matching 02: Obtain the test image
dev_set_draw ('margin')
read_image (Image, ImageFiles[Index1])
*read_image (Image, 'C:/Users/admin/Desktop/34POTTING/NGImage 20230322 152337224.bmp')
dev_display (Image)
* Matching 02: Find the model
find_shape_model (Image, ModelID, rad(0), rad(360), 0.1, 1, 0.5, 'least_squares', [6,1], 0.7, center_Row, center_Column, center_Angle, Score)
* Matching 02: Transform the model contours into the detected positions
tuple_length (center_Row, Length)
if (Length > 0)
* 进行模板的定位检测
hom_mat2d_identity (HomMat2D)
hom_mat2d_rotate (HomMat2D, center_Angle, 0, 0, HomMat2D)
hom_mat2d_translate (HomMat2D, center_Row - 318/2, center_Column-619/2, HomMat2D)
affine_trans_region (Rectangle15, RegionAffineTrans, HomMat2D, 'nearest_neighbor')
dev_set_color ('green')
disp_cross (WindowHandle, center_Row, center_Column, 46, center_Angle)
dev_display (RegionAffineTrans)
else
set_tposition (WindowHandle, 30, 30)
write_string (WindowHandle, 'slider查询失败')
endif
* 实现三个圆和直线的检测
points :=[]
rgb1_to_gray (Image, GrayImage)
for index:=0 to 2 by 1
*step1创建计量模型的初始对象
dev_get_window (WindowHandle)
left_center_r:=0
left_center_c:=0
* Radius:=0
* 获取左侧圆圆心
if(index==0)
reduce_domain (Image, Rectangle_LC, ImageReduced)
rgb1_to_gray (ImageReduced, Grayimg_LC)
dev_set_draw ('margin')
threshold (Grayimg_LC, Region, 0, 128)
connection (Region, ConnectedRegions)
select_shape (ConnectedRegions, SelectedRegions, ['area','inner_radius','circularity'], 'and', [7005.55,40.018,0.8364], [9889.09,59.982,1])
count_obj (SelectedRegions, Number)
if(Number>1)
circularity (SelectedRegions, Circularity)
tuple_sort_index (Circularity, Indices_tem)
select_obj (SelectedRegions, SelectedRegions, Indices_tem[Number-1]+1)
endif
area_center (SelectedRegions, Area2, Row_cer, Column_cer)
left_center_r := Row_cer
left_center_c := Column_cer
Radius:= 50
gen_circle (Circle, left_center_r, left_center_c, Radius)
endif
if(index==1)
find_shape_model (Image, ModelID_RU, rad(0), rad(360), 0.1, 1, 0.5, 'least_squares', [6,1], 0.7, left_center_r, left_center_c, c_Angle, c_Score)
Radius:= 25
gen_circle (Circle, left_center_r, left_center_c, Radius)
endif
if(index==2)
find_shape_model (Image, ModelID_RD, rad(0), rad(360), 0.1, 1, 0.5, 'least_squares', [6,1], 0.7, left_center_r, left_center_c, c_Angle, c_Score)
Radius:= 25
gen_circle (Circle, left_center_r, left_center_c, Radius)
endif
* draw_circle (WindowHandle, left_center_r, left_center_c, Radius)
* gen_circle (Circle, left_center_r, left_center_c, Radius)
*step2创建2D计量模型
create_metrology_model (MetrologyHandle)
get_image_size (GrayImage, Width, Height)
set_metrology_model_image_size (MetrologyHandle, Width, Height)
* step3: 查验添加到计量模型中的预检测轮廓
add_metrology_object_circle_measure (MetrologyHandle, left_center_r, left_center_c, Radius, 10, 4, 1, 20, [], [], Index)
get_metrology_object_model_contour (Contour, MetrologyHandle, 0, 1.5) // 传入到计量模型中的集合轮廓
get_metrology_object_measures (Contours, MetrologyHandle, 'all', 'positive' , Row1, Column1) // 测量矩形轮廓
* step4: 应用计量模型
apply_metrology_model (GrayImage, MetrologyHandle)
* step6: 查看测量结果
get_metrology_object_result (MetrologyHandle, 0, 'all', 'result_type', 'all_param', Parameter) //Parameter:中心点和半径
gen_cross_contour_xld (Cross, Parameter[0], Parameter[1], 16, 0.785398)
get_metrology_object_result_contour (Contour1, MetrologyHandle, 0, 'all', 1.5) // 获取的最终拟合到的轮廓
points[index*2] := Parameter[0]
points[index*2 + 1] := Parameter[1]
endfor
dev_clear_window ()
dev_get_window (WindowHandle)
dev_set_draw ('margin')
dev_display (Image)
*表示所有圆的中心坐标都检测到了,进行几何计算
tuple_length (points, Length1)
if (Length1 == 6)
* 显示三个中心点
gen_cross_contour_xld (Cross1, points[0], points[1], 36, 0.785398)
gen_cross_contour_xld (Cross2, points[2], points[3], 36, 0.785398)
gen_cross_contour_xld (Cross3, points[4], points[5], 36, 0.785398)
gen_cross_contour_xld (Cross4, center_Row, center_Column, 25, 0.785398)
dev_set_color ('red')
dev_display (Cross1)
dev_display (Cross2)
dev_display (Cross3)
dev_display (Cross4)
dev_set_color ('green')
dev_display (RegionAffineTrans)
* 计算垂足点
projection_pl (points[0], points[1], points[2], points[3], points[4], points[5], RowProj, ColProj)
gen_cross_contour_xld (Cross5, RowProj, ColProj, 36, 0.785398)
dev_set_color ('white')
dev_display (Cross5)
* 绘制直线
disp_line (WindowHandle, points[0], points[1], RowProj, ColProj)
disp_line (WindowHandle, points[2], points[3], points[4], points[5])
* 计算距离
distance_pl (center_Row, center_Column, points[0], points[1], RowProj, ColProj, Dis_r)
distance_pl (center_Row, center_Column, points[2], points[3], points[4], points[5], Dis_c)
* 计算距离包含正负:
projection_pl (center_Row, center_Column, points[0], points[1], RowProj, ColProj, RowProj1, ColProj1)
if(center_Row < RowProj1)
Dis_r := -Dis_r
endif
dev_set_color ('green')
gen_cross_contour_xld (Cross8, RowProj1, ColProj1, 25, 0.785398)
dev_display (Cross8)
* 显示坐标和距离值
dev_set_color ('red')
set_tposition (WindowHandle, center_Row+40, center_Column-100)
write_string (WindowHandle, ' Dis_r:' + Dis_r$'.2f' + ' Dis_c:' + Dis_c$'.2f' + ' angle:' + center_Angle$'.2f')
set_tposition (WindowHandle, points[0]-30, points[1])
write_string (WindowHandle, ' r: ' + points[0]$'.2f' + ' c: ' + points[1]$'.2f')
set_tposition (WindowHandle, points[2]-30, points[3])
write_string (WindowHandle, ' r: ' + points[2]$'.2f' + ' c: ' + points[3]$'.2f')
set_tposition (WindowHandle, points[4]+30, points[5])
write_string (WindowHandle, ' r: ' + points[4]$'.2f' + ' c: ' + points[5]$'.2f')
set_tposition (WindowHandle,RowProj - 20, ColProj + 60)
write_string (WindowHandle, 'r: ' + RowProj$'.2f' + ' c: ' + ColProj$'.2f')
set_tposition (WindowHandle,center_Row-60, center_Column-100)
write_string (WindowHandle, ' r: ' + center_Row$'.2f' + ' c: ' + center_Column$'.2f')
* 保存图像
parse_filename (ImageFiles[Index1], BaseName, Extension, Directory)
save_file := 'C:/Users/admin/Desktop/new11-20/20-9/' + BaseName
dump_window_image (Image2, WindowHandle)
write_image (Image2, 'bmp', 0, save_file)
* 将结果数据写入到.txt文件
list_re := [BaseName,center_Row,center_Column,Dis_r,Dis_c,center_Angle]
tuple_length (list_re, Length2)
for Index2 := 0 to Length2-1 by 1
fwrite_string (FileHandle, list_re[Index2])
fwrite_string (FileHandle, ' ')
endfor
fnew_line(FileHandle)
endif
stop ()
endfor
close_file (FileHandle)