主要是找线,遍历迭代跟踪起点和终点获得轮廓。方法可借鉴。
C:\Users\Public\Documents\MVTec\HALCON-23.05-Progress\examples\hdevelop\Applications\Measuring-2D\track_wires_on_chip.hdev
初始化操作
读入图并显示
获取搜索区域,根据已知的起点
初步筛选contour
角度筛选
连接共线轮廓
找到距离起点最近的轮廓
连接为线的一部分
更新起点,以及角度,搜索范围的半径等参数
判断终点
判断最小值,如果大于5,反向重新查找得到线段。
反向查找:
如果不是,直接联合在一起
其中有3个函数
第一个:可借鉴
获取扇形的搜索区域
gen_search_region (SearchRegion, StartPointRow, StartPointCol,
RefOrientation, min([SearchRadius,Distance]), AngleTolerance)
gen_ellipse_contour_xld (ContEllipse, StartRow, StartColumn, SearchAngle,\
SearchRadius, SearchRadius, -AngleTolerance,\
AngleTolerance, 'positive', 1.5)
get_contour_xld (ContEllipse, ContRow, ContCol)
ContRow := [StartRow,ContRow]
ContCol := [StartColumn,ContCol]
gen_contour_polygon_xld (Cone, ContRow, ContCol)
gen_region_contour_xld (Cone, SearchRegion, 'filled')
return ()
第二个:可借鉴
- 尝试连接剩余的候选轮廓。
union_collinear_wire_segments (ObjectSelected, UnionContours, 5, 0.5)
* 通常在芯片周围会有暗区,这些暗区对应于阴影,
* 会在跟踪键合线时产生间隙。我们需要通过连接独立的线段来跨越这些间隙。
*
* 下面的思路是:线段越长,这些线段实际上对应于键合线的可能性就越大,
* 因此,即使它们之间的间隔较大,我们也决定将它们连接起来。
length_xld (Segments, Lengths)
MaxDistAbs := max(Lengths)
MaxDistRel := max(Lengths) / min(Lengths)
union_collinear_contours_ext_xld (Segments, UnionContours, MaxDistAbs, MaxDistRel, 2, MaxAngle, 0, -1, 1, 1, 1, 1, 1, 0, 'attr_keep')
*
Iter := 0
length_xld (UnionContours, Lengths)
while (Iter < MaxIterations and |Lengths| > 1)
MaxDistAbs := 0.4 * max(Lengths)
MaxDistRel := 0.1 * max(Lengths) / min(Lengths)
union_collinear_contours_ext_xld (UnionContours, UnionContours, MaxDistAbs, MaxDistRel, 2, 0.5, 0, -1, 1, 1, 1, 1, 1, 0, 'attr_keep')
Iter := Iter + 1
endwhile
return ()
第三个:* * 更新搜索参数以用于下一次迭代。
update_search_parameters (WireSegment, StartPointRow, StartPointCol, RefOrientation, MinSearchRadius, MaxDistSegment, StartPointRow, StartPointCol, RefOrientation, SearchRadius, DistSegmentThreshold)
函数实现:
RefOrientationOut := RefOrientation
StartPointColOut := StartPointCol
StartPointRowOut := StartPointRow
*
* 找到轮廓上距离当前起点最远的极点,并将其定义为下一次迭代的新的起始搜索点。
get_contour_xld (WireSegment, Row, Col)
EndPointsRow := [Row[0],Row[|Row| - 1]]
EndPointsCol := [Col[0],Col[|Col| - 1]]
gen_region_line (RegionLines, EndPointsRow, EndPointsCol, [StartPointRowOut,StartPointRowOut], [StartPointColOut,StartPointColOut])
distance_pp (EndPointsRow, EndPointsCol, [StartPointRowOut,StartPointRowOut], [StartPointColOut,StartPointColOut], Distance)
if (Distance[0] > Distance[1])
StartPointRowOut := EndPointsRow[0]
StartPointColOut := EndPointsCol[0]
else
StartPointRowOut := EndPointsRow[1]
StartPointColOut := EndPointsCol[1]
endif
*
* 计算局部轮廓的方向,并将其定义为下一次迭代的参考角度。其基本思想是,
*键合线在相邻的迭代(线段)之间弯曲得非常平滑(弯曲角度小)。
orientation_points_xld (WireSegment, Orientation)
OrientationDiffAbs := fabs(fmod(Orientation - RefOrientation,rad(360)))
if (OrientationDiffAbs > rad(90) and OrientationDiffAbs < rad(270))
Orientation := Orientation + rad(180)
endif
while (Orientation < -rad(180))
Orientation := Orientation + rad(360)
endwhile
while (Orientation > rad(180))
Orientation := Orientation - rad(360)
endwhile
RefOrientationOut := Orientation
*
* 根据当前线段的长度定义搜索域的半径:线段越长,半径越大。
*其基本思想是,较长的线段对应于弯曲程度很小的键合线部分。
*因此,我们可以通过扩大搜索范围来加快跟踪速度。
length_xld (WireSegment, SearchRadius)
SearchRadius := 1.5 * max([SearchRadius,MinSearchRadius])
DistSegmentThreshold := min([SearchRadius / 2.0,MaxDistSegment])
return ()
第4个:跟踪到线的区域 track_wire (Image, FwdWireSegments, StartPadRows[Pad], StartPadCols[Pad], StartInitOrientation[Pad], EndPadRows[Pad], EndPadCols[Pad], WireWidths[Img - 1], AngleTolerance, MinSegmentLength, MaxDistSegment)
gen_empty_obj (Wire)
get_image_size (Image, Width, Height)
* 重新开始跟踪键合线的点的坐标
StartPointRow := StartPadRows
StartPointCol := StartPadCols
* 上一次找到的线段的方向。它作为下一次迭代搜索的参考。
* 基本思想是,线的曲率在每次迭代之间不会发生太大变化。
RefOrientation := StartInitOrientation
* 键合线应当位于其中的圆形区域的半径。
SearchRadius := 30 * WireWidths
MinSearchRadius := 5 * WireWidths
DistSegmentThreshold := min([SearchRadius / 3.0,MaxDistSegment])
while (1)
distance_pp (StartPointRow, StartPointCol, EndPadRows, EndPadCols, Distance)
gen_search_region (SearchRegion, StartPointRow, StartPointCol, RefOrientation, min([SearchRadius,Distance]), AngleTolerance)
reduce_domain (Image, SearchRegion, ImageReduced)
* 迭代搜索候选点。当图像对比度较低时,
* 我们需要降低高阈值。
NXLDs := 0
HighThreshold := 1
while (NXLDs == 0 and HighThreshold >= 0.1)
lines_gauss (ImageReduced, Lines, WireWidths / sqrt(3), 0.05, HighThreshold, 'dark', 'true', 'true', 'true')
gen_polygons_xld (Lines, Polygons, 'ramer', 1)
split_contours_xld (Polygons, Contours, 'polygon', 1, 5)
select_contours_xld (Contours, SelectedContours, 'contour_length', MinSegmentLength, max([Width,Height]) / 2.0, -0.5, 0.5)
count_obj (SelectedContours, NXLDs)
HighThreshold := 0.5 * HighThreshold
endwhile
if (NXLDs == 0)
break
endif
* 跟踪键合线
* 首先,选择与前一段线具有相似方向的轮廓。
LowerLimit := [RefOrientation - AngleTolerance,RefOrientation - AngleTolerance + rad(180),RefOrientation - AngleTolerance - rad(180)]
UpperLimit := [RefOrientation + AngleTolerance,RefOrientation + AngleTolerance + rad(180),RefOrientation + AngleTolerance - rad(180)]
select_shape_xld (SelectedContours, ObjectSelected, ['phi_points', 'phi_points', 'phi_points'], 'or', LowerLimit, UpperLimit)
*
* 在这些轮廓中,选择与前一个最接近的轮廓。
count_obj (ObjectSelected, NContours)
if (NContours > 1)
* * 尝试连接剩余的候选轮廓。
union_collinear_wire_segments (ObjectSelected, UnionContours, 5, 0.5)
count_obj (UnionContours, NUCont)
DistMin := []
** 如果仍然有多个候选轮廓,
* * 选择最接近起点的那个。
for C := 1 to NUCont by 1
select_obj (UnionContours, ObjectSelected1, C)
distance_pc (ObjectSelected1, StartPointRow, StartPointCol, DistanceMin, DistanceMax)
DistMin := [DistMin,DistanceMin]
endfor
tuple_find (DistMin, min(DistMin), IndMin)
if (DistMin[IndMin[0]] > DistSegmentThreshold)
break
endif
select_obj (UnionContours, WireSegment, IndMin[0] + 1)
elseif (NContours == 0)
break
else
* * 只有一个候选轮廓。
copy_obj (ObjectSelected, WireSegment, 1, 1)
endif
concat_obj (Wire, WireSegment, Wire)
* * 更新搜索参数以用于下一次迭代。
update_search_parameters (WireSegment, StartPointRow, StartPointCol, RefOrientation, MinSearchRadius, MaxDistSegment, StartPointRow, StartPointCol, RefOrientation, SearchRadius, DistSegmentThreshold)
** 检查轮廓是否已经到达终点。
*
* * V1:从起点(StartPoint)指向终点(EndPoint)的单位向量
V1 := [EndPadRows - StartPointRow,EndPadCols - StartPointCol]
V1 := V1 / sqrt(sum(V1 * V1))
* V2:沿着键合线方向从起点(StartPoint)出发的单位向量
V2 := [-sin(RefOrientation),cos(RefOrientation)]
if (V1[0] * V2[0] + V1[1] * V2[1] < 0)
break
endif
endwhile
return ()
第二个:* 将线的所有线段合并为一个覆盖整个轨迹的长轮廓
fuse_wire_segments (FwdWireSegments, Wire, StartPadRows[Pad], StartPadCols[Pad], EndPadRows[Pad], EndPadCols[Pad], MinWireLength, MaxDistSegment, MinWireDeviation)
count_obj (WireSegments, NumWires)
if (NumWires > 0)
smooth_contours_xld (WireSegments, SmoothedContours, 9)
union_adjacent_contours_xld (SmoothedContours, UnionContours, MaxDistSegment, 3, 'attr_keep')
select_longest_contour (UnionContours, Wire, Length)
if (Length < MinWireLength)
gen_empty_obj (Wire)
return ()
endif
* 有时,跟踪过程可能会超出终点,因为它在图像中发现了类似于线的结构
*(一个细长区域,其暗像素被两侧较亮的像素包围)。
* 我们对最终的轮廓进行裁剪,使其在终点处停止。
crop_contours_xld (Wire, Wire, min([StartPadRows,EndPadRows]) - MinWireDeviation, min([StartPadCols,EndPadCols]) - MinWireDeviation, max([StartPadRows,EndPadRows]) + MinWireDeviation, max([StartPadCols,EndPadCols]) + MinWireDeviation, 'true')
select_longest_contour (Wire, Wire, MaxLength)
* 确保轮廓的方向是从起始焊盘指向终止焊盘。
DPRow := EndPadRows - StartPadRows
DPCol := EndPadCols - StartPadCols
get_contour_xld (Wire, Row, Col)
if (fabs(DPRow) > fabs(DPCol))
DCRow := Row[|Row| - 1] - Row[0]
if (DPRow * DCRow < 0)
gen_contour_polygon_xld (Wire, inverse(Row), inverse(Col))
endif
else
DCCol := Col[|Col| - 1] - Col[0]
if (DPCol * DCCol < 0)
gen_contour_polygon_xld (Wire, inverse(Row), inverse(Col))
endif
endif
endif
return ()
* 本程序展示了如何迭代跟踪键合线。
* 它假设以下值来自之前的初始化阶段:
* 1. 对应于键合点的起点和终点的坐标。
* 2. 线从上述各点出发的角度。
* 3. 近似的线宽(以像素为单位)。
dev_update_off ()
dev_close_window ()
read_image (Image, 'die/die_01')
get_image_size (Image, Width, Height)
dev_open_window (0, 0, 500, 500, 'black', WindowHandle)
set_display_font (WindowHandle, 16, 'mono', 'true', 'false')
dev_set_draw ('margin')
dev_set_colored (12)
dev_set_line_width (2)
*
* 搜索参数 ********************************
*图像中的铜线的大约半宽度
WireWidths := [1.8, 0, 0, 8, 6, 1.7, 7.5]
* 可能对应于键合线的轮廓的最小长度
MinSegmentLength := 5
* 角度搜索范围
AngleTolerance := rad(35)
* 定义搜索区域的圆形部分的半径
MinSearchRadius := 10
MinWireDeviation := 5
* 以终点为中心的圆的半径,
* 跟踪算法必须到达该圆内,
* 才能认为跟踪成功。
AcceptanceRadius := 5
MinWireLength := 80
* ***************************************************
*
*
for Img := 1 to 7 by 1
try
read_tuple ('die_' + Img$'02' + '_params.tup', Parameters)
catch (Exception)
continue
endtry
NPads := Parameters[0]
StartPadRows := Parameters[1:NPads]
StartPadCols := Parameters[NPads + 1:2 * NPads]
StartInitOrientation := Parameters[2 * NPads + 1:3 * NPads]
EndPadRows := Parameters[3 * NPads + 1:4 * NPads]
EndPadCols := Parameters[4 * NPads + 1:5 * NPads]
EndInitOrientation := Parameters[5 * NPads + 1:6 * NPads]
gen_empty_obj (Wires)
read_image (Image, 'die/die_' + Img$'02')
dev_display (Image)
for Pad := 0 to |StartPadRows| - 1 by 1
* 两条连续轮廓之间对应于同一根线的最大距离
MaxDistSegment := 17 * WireWidths[Img - 1]
gen_region_line (RegionLines, StartPadRows[Pad], StartPadCols[Pad], EndPadRows[Pad], EndPadCols[Pad])
track_wire (Image, FwdWireSegments, StartPadRows[Pad], StartPadCols[Pad], StartInitOrientation[Pad], EndPadRows[Pad], EndPadCols[Pad], WireWidths[Img - 1], AngleTolerance, MinSegmentLength, MaxDistSegment)
* 将线的所有线段合并为一个覆盖整个轨迹的长轮廓
fuse_wire_segments (FwdWireSegments, Wire, StartPadRows[Pad], StartPadCols[Pad], EndPadRows[Pad], EndPadCols[Pad], MinWireLength, MaxDistSegment, MinWireDeviation)
*
* 检查算法是否能够成功跟踪整根线
*如果不能,尝试从终点开始反向跟踪到起点,并合并结果。
distance_pc (Wire, EndPadRows[Pad], EndPadCols[Pad], DistanceMin, DistanceMax)
if (DistanceMin > AcceptanceRadius)
* 新的终点对应于算法在上一次尝试中离开的轮廓的终点。
count_obj (FwdWireSegments, NXLDs)
select_obj (FwdWireSegments, ObjectSelected, NXLDs)
get_contour_xld (ObjectSelected, Row, Column)
track_wire (Image, BckWireSegments, EndPadRows[Pad], EndPadCols[Pad], EndInitOrientation[Pad], StartPadRows[Pad], StartPadCols[Pad], WireWidths[Img - 1], AngleTolerance, MinSegmentLength, MaxDistSegment)
fuse_wire_segments (BckWireSegments, WireBck, StartPadRows[Pad], StartPadCols[Pad], EndPadRows[Pad], EndPadCols[Pad], DistanceMin, MaxDistSegment, MinWireDeviation)
count_obj (WireBck, WiresFound)
if (WiresFound > 0)
* 找到正向轮廓上最接近反向轮廓的点。
distance_contours_xld (Wire, WireBck, ContourOut, 'point_to_segment')
query_contour_attribs_xld (ContourOut, Attribs)
get_contour_attrib_xld (ContourOut, 'distance', Distance)
MinDistIdx := sort_index(Distance)[0]
* 在该点处截断正向轮廓
get_contour_xld (Wire, WireRow, WireCol)
gen_contour_polygon_xld (WireFwdPart, WireRow[0:MinDistIdx], WireCol[0:MinDistIdx])
* 找到反向轮廓上最接近正向轮廓新终点的点。
get_contour_xld (WireBck, WireBckRow, WireBckCol)
distance_pp (WireBckRow, WireBckCol, gen_tuple_const(|WireBckRow|,WireRow[MinDistIdx]), gen_tuple_const(|WireBckCol|,WireCol[MinDistIdx]), DistancePP)
MinDistBckIdx := sort_index(DistancePP)[0]
* 在该点处截断反向轮廓。
gen_contour_polygon_xld (WireBckPart, WireBckRow[MinDistBckIdx + 1:|WireBckRow| - 1], WireBckCol[MinDistBckIdx + 1:|WireBckRow| - 1])
*将两个轮廓线段拼接在一起(注意它们已经通过`fuse_wire_segments`过程一致地定向了)
concat_obj (WireFwdPart, WireBckPart, WireSegments)
else
count_obj (FwdWireSegments, NSegments)
Sequence := [1:NSegments]
select_obj (FwdWireSegments, WireSegments, Sequence)
endif
* * 合并最终的轮廓。
length_xld (WireSegments, Length)
union_adjacent_contours_xld (WireSegments, FusedSegments, max(Length), max(Length) / max([min(Length),1]), 'attr_keep')
smooth_contours_xld (FusedSegments, Wire, 11)
concat_obj (Wires, Wire, Wires)
else
smooth_contours_xld (Wire, SmoothedContours1, 11)
concat_obj (Wires, SmoothedContours1, Wires)
endif
endfor
dev_display (Image)
dev_display (Wires)
if (Img < 7)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
endif
endfor