一,在讲基于Halcon基于描述符的模板匹配前,先讲一个算子。SIFT,即尺度不变特征变换(Scale-invariant feature transform,SIFT),是用于图像处理领域的一种描述。这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征描述子。SIFT特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、微视角改变的容忍度也相当高。
SIFT算法具有如下一些特点:
1.SIFT特征是图像的局部特征,其对旋转、尺度缩放、亮度变化保持不变性,对视角变化、仿射变换、噪声也保持一定程度的稳定性;
2. 区分性(Distinctiveness)好,信息量丰富,适用于在海量特征数据库中进行快速、准确的匹配;
3. 多量性,即使少数的几个物体也可以产生大量的SIFT特征向量;
4.高速性,经优化的SIFT匹配算法甚至可以达到实时的要求;
5.可扩展性,可以很方便的与其他形式的特征向量进行联合。
二、Halcon基于描述符的模板检测原理类似于SIFT算法。同样是提前关键点,模板与轮廓无关。它的模板不是根据边缘轮廓创建的,而是根据特征点创建的。
例如:点的位置或者相邻像素的灰度信息等都可以作为描述符。有纹理的平面图形非常适用于这种方法,尤其是对于旋转倾斜等场景中的匹配可以得到非常理想的结果。基于描述符的匹配允许一定程度的透视形变,并且能在有标定和无标定的图像中进行。基于描述符的匹配与物体的轮廓无关,而是与目标的纹理密切相关,或者说与目标的特征点相关。
注:基于描述符的模板匹配只能用于有纹理的图像。
创建基于描述符的模板这一步会比较耗时,所用的时间与纹理的复杂度有关。
三、算子
ROI可以是任意的形状,甚至可以是不连接的多个区域
创建合适的描述Model
使用以下两种方法创建模型
Create_uncalib_descriptor_model
Create_calib_descriptor_model
一个是基于calibrated的摄像机,另一个是基于未校准的摄像机的。Calibrated摄像机需要相机的内参和reference pose,其余的两个函数的参数一样。
下面我们看一下具体的参数说明,我们说明一个怎样
选择和适配检测器
Adjust the descriptor
对于calibrated matching,指定相机参数和相应的pose
模型在创建以后是可以进行查看和修改的。
选择和适配检测器(DetectorType,DetectorParamName, DetectorParamValue)
从图像中检测出interest points的方法就叫做检测器(detector)。
DetectorType指定检测方法。可能的值为lepetit,harris,harris_binomial.(不知道为什么halcon中不包含sift surf,orb等检测算子)。Lepetit是一种检测速度非常快的方法检测重要的点,但是鲁棒性不如harris。特别地,当模板或者搜索的图像很暗或者有一个很低的对比度,不要使用lepetit。Harris_binomial是一个好的折衷,它比harris快,但是比lepetit鲁棒。
对于每种检测器类型,都可以使用一组通用参数,这些参数可以通过参数detectorParamName和detectorParamValue进行调整。第一个用于指定通用参数的名称,第二个用于指定对应的值。我们建议在创建模型之前使用选定的点运算符进行测试。也就是说,将相应的点运算符应用于模板图像,并使用比如gen_corss_contour_xld。为了获得良好的效果,模板图像中应该均匀分布大约50到450个点。如果已找到所选点运算符的适当参数设置,也可以为模型通过create_calib_descriptor_model或者create_uncalib_descriptor_model设置这些参数. 注意,在大多数情况下,探测器的默认值(DetectorParamName和DetectorParamValue设置为[])都是效果已经很好了。
Adjust the descriptor(DescriptorParamName DesctiptorParamValue)
现在实现的描述符使用随机fern对提取点进行分类,建立兴趣点位置特征描述和局部灰度邻域。
可以使用参数descriptorParamName和descriptorParamValue调整描述符。descriptorParamName用于指定必须调整的通用参数的名称,descriptorParamValue用于指定相应的值。
有两种参数,一种是控制描述符大小的参数,控制鲁棒性、速度、内存使用。另一种是模拟参数,模型视图训练的空间范围。
Descriptor Size由以下参数控制:
depth—指定分类fern的深度,深度越大,interest points可以更好的区分出来,但是导致计算时间增加。
number_ferns—指定fern结构的数量 ,数量越多,鲁棒性越强,同样增加计算时间
patch_size—指定描述子领域的边长,同样,太大增加计算时间,太小不能提取出准确的特征。
Depth和number的参数需要按照不同的需要进行设定,如果需要快速的匹配,建议使用少量的depth和较大的number_ferns。如果你需要一个robust的匹配,那么就需要较大的depth,number_ferns也需要设置的大一些,同时,运算速度会明显下降。如果对内存要求很严格,建议使用较小的number_ferns。对于许多应用程序,需要在不同的需求之间进行权衡。
Simulation 控制参数控制the train of the model
tilt –在simulation阶段打开或者关闭投影变换。打开会增加鲁棒性,但是计算时间也增加
min_rot和max_rot(min_rotation)定义model的法向量旋转的角度范围
min_scale和max_scale 定义model的缩放范围。
设置小范围的旋转角度和小范围的scale可以明显加快训练速度。但是,在以后的应用匹配过程中,只有当模型的角度和scale在训练的范围内时,才能找到。另外,小图像训练的速度更快,因此,可以通过选择一个小参考图像和小模板图像并将“tilt’(倾斜)设置为关闭来加速训练。但是参考图像和搜索图像的大小应该相同。所以在实际应用中,应该把大图先进行一下缩放,然后进行模板训练,然后采集的实际图像也进行相同的缩放,然后进行搜索。这样来加速。这和神经网络有些相似,神经网络输入不能太大,所以在输入端是比较小的图像。
create_uncalib_descriptor_model (ImageReduced, ‘harris_binomial’, [],
[], [‘min_rot’,‘max_rot’,‘min_scale’,
‘max_scale’], [-90,90,0.2,1.1], 42,
ModelID)
指定相机参数和reference Pose(camParam,ReferencePose)
对于一个校准相机匹配,需要指定相机参数和相机位姿。我们建议使用相机标定的方式获取这两个参数。另一种确定平面姿态的其它方法是通过手动测量模型的范围,这种方法相当复杂,通常也不准确。比如(立体视觉,3d激光三角测量)
查看和修改descriptor Model
可以通过get_descriptor_model_points查看model中的兴趣点。通过get_descriptor_model_params查看模型的参数。如果model是其它的程序创建的或者模型创建的时候使用的自动参数,这个方法可以查看model的参数。使用write_descriptor_model,来保存模型,使用read_descriptor_model读取模型。另外,使用get_descriptor_model_origin查询模型的坐标原点。
模型也可以修改,使用set_descriptor_model_origin来改变原点,但是不建议这么做,因为匹配精度会下降。对于您仍然需要修改参考点并且要应用校准匹配的情况,我们建议参考透视变形匹配的相应描述。在此,介绍了参考姿态、模型姿态和手动应用于参考点的偏移之间的关系。
在匹配之后,你可以使用get_descriptor_model_points来查询匹配到的interest points.参数Set需要设置为’search’而不是‘model’,下面的代码显示了第一个匹配到的模型interest points。
get_descriptor_model_points (ModelIDs[Index2], ‘search’, 0, Row, Col)
另外在匹配之后 ,get_descriptor_model_results可用于查询在搜索过程中积累的所选数值结果,如单个搜索点和模型点之间对应关系的得分。(ResultNames set to ‘point_classification’)
优化搜索过程
要在相同或类似对象的未知图像中定位模型中存储和描述的相同兴趣点,将应用以下运算符:
find_calib_descriptor_model搜索calibrated descriptor model最好的匹配。返回一个object的3D 位姿和一个匹配的得分。
find_uncalib_descriptor_model 搜索uncalibrated descriptor model最好的匹配。它返回一个二维射影变换矩阵(单应矩阵)和描述匹配质量的分数。
两个函数只差一个CamParam参数。其它的是一样的。这里假设使用和创建模型时一样的摄像机参数。如果使用一个不同的摄像机,我们建议重新进行摄像机标定。这里也就是说可以使用不同的相机进行搜索,只要有相机参数即可。
下面我们来说一下:
限制搜索空间(ROI)
调整探测器进行搜索,这仅在极少数情况下推荐
调整搜索的描述符
设置相似性得分阈值,MinScore
设置NumMatches,搜索几个目标
选择一个得分类型(SocreType)
在匹配结束后需要调用clear_descriptor_model从内存中销毁。
限制搜索空间
限制搜索空间能够加速匹配。其实就是设置一个开关,然后通过reduce_main保留其中一个区域,在此区域中进行查找。
调整detector进行搜索
主要是修改DetectorParamName和DetectorParamValue,在创建Model中我们已经提到过。大多数情况下,这些值不需要修改,也就是把使用一个空的tuple([ ])作为参数即可。
在少数情况下,尤其是当参考图像和搜索图像之间有明显的光照变化时,您可以更改参数值。例如,如果搜索图像很“暗”然后我们还使用了“lepetit”选作探测器,你可以将“min_score”设置为一个更小的值
一般来说,为了测试是否需要更改参数值,您可以将探测器对应的点运算符不仅应用于为创建模型而建议的参考图像,还应用于搜索图像。另外,为了得到一个良好的匹配结果,应该提取大约50到450个均匀分布的点。如果参考图像和搜索图像使用的点运算符(lepetit,harris等)需要不同的参数,则可能需要相应地更改相应检测器参数的值。
调整descriptor for search(DescriptorParamName, DescriptorParamValue)
控制兴趣点之间对应关系的确定的参数。通过描述符参数名称和描述符参数值调整搜索图像和模型图像。可以设置两个通用参数:
min_score_descr—这个参数可以设置为大于0的任何值(最好低于0.1),这是检测到的分数阈值,这会值设置的低会减少用于进行计算的点数,从而提高匹配速度。但是会降低鲁棒性,特别是在提取点的数量比较低的情况下。
guided_matching—这有两个值,on 和 off,(默认是on)如果启动guided matching,则模型的位置估计的鲁棒性将得到增强。特别是,从搜索图像中提取点并通过描述符进行分类。分类所接受的点用于计算初始投影二维(未校准情况)或齐次三维(校准情况)变换矩阵。然后用于投影所有模型点到搜索图像中。如果投影模型点接近原始提取点之一,即独立于其分类,则该点用于最终计算通过匹配分别返回为二维投影变换矩阵或三维姿势的同形性。一般来说,如果没有分类,可以使用更多的点进行计算,得到的同形图更为可靠。另一方面,在某些情况下,匹配的速度可能增加到10%。因此,如果稳健性不如速度重要,则可以关闭“引导匹配”。
设置相似性得分阈值(minScore)
参数minscore指定潜在匹配必须作为匹配返回的最小分数。分数是匹配质量的值,即模型和搜索图像之间的对应关系或“相似性”。注意,对于基于描述符的匹配,输出参数得分有不同类型的得分。但对于输入参数minscore,始终使用“inlier_ratio”类型的分数。它计算点对应数量与模型点数量的比率。在大多数情况下,minscore应设置为至少0.1的值。为了加快搜索速度,这个值应该设置的大一些,但是,这个值又不能太大,太大就是要求完全匹配了,所以此值又不能太大。例如,匹配不太可能达到1.0的值。
设置NumMatches,搜索几个目标(NumMatches)
设置最大目标数量。如果设置为0,就是返回所有的匹配。定位多于一个目标时,返回的结果是tuple,而不是一个值,Score,HomMat2D ,Pose等。多个object的搜索只是比搜索一个目标慢一点,不会差太多。
选择一个得分类型(ScoreType)
num_points–对于“num_points”,返回每个实例的点对应数。由于任何四个对应定义了两个图像之间数学上正确的同形性,因此该数字至少应为10,以假设一个可靠的匹配结果。
inlier_ratio–对于“inlier_ratio”,返回点对应数量与模型点数量的比率。虽然此参数的值介于0.0和1.0之间,但匹配不太可能达到1.0的比率。但是,如果对象的内部比率小于0.1,则应忽略该对象。
典型的,可以只用其中的一个也可以使用两个,使用两个的话需要把两个放入一个tuple中,结果也会返回一个tuple。
四、案例
4.1 没有标定的基于描述符模板匹配,Halcon源程序
这个例子在图片数据库中查找文章页面。
*在第一步中,训练不同的页面并创建模型。
*随后搜索未知图像和正确的文章
检测到几页。
*请注意,这个例子需要一些内存来训练模型。
dev_update_off ()
dev_close_window ()
read_image (Image, 'brochure/brochure_page_01')
get_image_size (Image, Width, Height)
dev_open_window_fit_image (Image, 0, 0, -1, -1, WindowHandle)
set_display_font (WindowHandle, 14, 'mono', 'true', 'false')
dev_set_draw ('margin')
dev_display (Image)
*
*清除所有已经创建的描述符模型。
ModelIDs := []
ModelsFound := 0
NumPoints := []
NumModels := 3
TotalTime := 0
*
*为可视化目的创建区域。
RowRoi := [10,10,Height - 10,Height - 10]
ColRoi := [10,Width - 10,Width - 10,10]
gen_rectangle1 (Rectangle, 10, 10, Height - 10, Width - 10)
disp_message (WindowHandle, ['Press \'Run\' to start model creation ...','(may take a few minutes)'], 'window', 10, 10, 'black', 'true')
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
*
*为每个页面创建描述符模型。
for Index := 1 to NumModels by 1
read_image (Image, 'brochure/brochure_page_' + Index$'.2')
rgb1_to_gray (Image, ImageGray)
get_image_size (ImageGray, Width, Height)
reduce_domain (ImageGray, Rectangle, ImageReduced)
dev_clear_window ()
dev_display (ImageGray)
disp_message (WindowHandle, 'Creating model no. ' + Index + '/' + NumModels + ' ... please wait.', 'window', 10, 10, 'black', 'true')
*
*使用默认参数创建描述符模型(缩放除外)
*为了快速检测,选择哈里斯二项点检测器。
count_seconds (Seconds1)
create_uncalib_descriptor_model (ImageReduced, 'harris_binomial', [], [], ['min_rot','max_rot','min_scale','max_scale'], [-90,90,0.2,1.1], 42, ModelID)
count_seconds (Seconds2)
TotalTime := TotalTime + (Seconds2 - Seconds1)
* 设置模板中心点为图像中心点(为了在后面的步骤中正确投影矩形的原点)
set_descriptor_model_origin (ModelID, -Height / 2, -Width / 2)
ModelIDs := [ModelIDs,ModelID]
* 从模板中获取所有的描述点
*存储从模型中提取的点,以备后续匹配。
get_descriptor_model_points (ModelID, 'model', 'all', Row_D, Col_D)
NumPoints := [NumPoints,|Row_D|]
endfor
*
* Model creation finished.
dev_display (ImageGray)
disp_message (WindowHandle, NumModels + ' models created in ' + TotalTime$'.4' + ' seconds.', 'window', 10, 10, 'black', 'true')
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
*
* Initialize the window again, because the image size has changed.
read_image (Image, 'brochure/brochure_01')
dev_resize_window_fit_image (Image, 0, 0, -1, -1)
set_display_font (WindowHandle, 14, 'mono', 'true', 'false')
*
* Main loop:
* Search the models in all images
for Index1 := 1 to 12 by 1
OutputString := []
NumMsgs := 0
ModelsFound := 0
TotalTime := 0
read_image (Image, 'brochure/brochure_' + Index1$'.2')
rgb1_to_gray (Image, ImageGray)
dev_display (Image)
disp_message (WindowHandle, 'Searching image ...', 'window', 10, 10, 'black', 'true')
*
* Search every model in each image
for Index2 := 0 to |ModelIDs| - 1 by 1
*
* Find model (using default parameters)
count_seconds (Seconds1)
find_uncalib_descriptor_model (ImageGray, ModelIDs[Index2], 'threshold', 600, ['min_score_descr','guided_matching'], [0.003,'on'], 0.25, 1, 'num_points', HomMat2D, Score)
count_seconds (Seconds2)
Time := Seconds2 - Seconds1
TotalTime := TotalTime + Time
*
* Check if the found instance is to be considered as a possible right match
* depending on the number of points which were considered
if ((|HomMat2D| > 0) and (Score > NumPoints[Index2] / 4))
*获取搜索到的描述点坐标
get_descriptor_model_points (ModelIDs[Index2], 'search', 0, Row, Col)
gen_cross_contour_xld (Cross, Row, Col, 6, 0.785398)
*
* 利用查找模板结果输出的投影变换矩阵,对原始模板区域仿射变换到当前找到的模板位置。
* 利用查找模板结果输出的投影变换矩阵,对原始坐标进行变换
projective_trans_region (Rectangle, TransRegion, HomMat2D, 'bilinear')
projective_trans_pixel (HomMat2D, RowRoi, ColRoi, RowTrans, ColTrans)
angle_ll (RowTrans[2], ColTrans[2], RowTrans[1], ColTrans[1], RowTrans[1], ColTrans[1], RowTrans[0], ColTrans[0], Angle)
Angle := deg(Angle)
*
* Check if the projected rectangle is to be considered as a right match
* depending on the angle in the right upper edge.
if (Angle > 70 and Angle < 110)
area_center (TransRegion, Area, Row, Column)
ModelsFound := ModelsFound + 1
dev_set_color ('green')
dev_set_line_width (4)
dev_display (TransRegion)
dev_set_colored (12)
dev_set_line_width (1)
dev_display (Cross)
disp_message (WindowHandle, 'Page ' + (Index2 + 1), 'window', Row, Column, 'black', 'true')
OutputString := [OutputString,'Page ' + (Index2 + 1) + ' found in ' + (Time * 1000)$'.4' + ' ms\n']
endif
endif
endfor
if (ModelsFound == 0)
OutputString := 'No model found'
endif
NumMsgs := NumMsgs + 1
OutputString := ['Search time over all pages: ' + (TotalTime * 1000)$'.4' + ' ms',OutputString]
disp_message (WindowHandle, OutputString, 'window', 10, 10, 'black', 'true')
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
endfor
dev_display (ImageGray)
disp_message (WindowHandle, 'Program finished.\nPress \'Run\' to clear all descriptor models.', 'window', 10, 10, 'black', 'true')
原图
结果图
五、注意事项
5.1 查找模板的输出结果只有分数和仿射变换矩阵。没有跟一般的模板匹配那样,输出找到的模板坐标。不过可以利用输出的矩阵HoMat2D,对原始模板区域和区域坐标做仿射变换,得到现在搜索到的模板区域和区域坐标。