荒廢半個(gè)寒假,是時(shí)候開啟新的旅途了~~
這是我學(xué)習(xí)HALCON的第一個(gè)例程,一步一個(gè)腳印吧!

程序流程
首先讀入圖像并進(jìn)行二值化
read_image (Bond, 'die/die_03')
threshold (Bond, Bright, 100, 255)
二值化后的圖像如下,分割了PCB的大部分區(qū)域

使用這個(gè)函數(shù)可以變換區(qū)域的形狀。其中第三個(gè)參數(shù)可以控制變換的策略,這里是變換成區(qū)域的最小外包矩形

使用上面的最小外包矩形來(lái)選定ROI,再次二值化并孔洞填充就可以得到更小的包含焊點(diǎn)的區(qū)域
* 摳圖(選中ROI)
reduce_domain (Bond, Die, DieGrey)
threshold (DieGrey, Wires, 0, 50)
* 孔洞填充
fill_up_shape (Wires, WiresFilled, 'area', 1, 100)

使用圓形的結(jié)構(gòu)元素來(lái)進(jìn)行閉運(yùn)算,將細(xì)小的區(qū)域去掉。完成后還有線路上的矩形焊點(diǎn)需要處理
opening_circle (WiresFilled, Balls, 15.5)

找到連通域并根據(jù)區(qū)域的幾何特征來(lái)選擇正確的焊點(diǎn)。這里使用的是區(qū)域的圓度,即第三個(gè)參數(shù)circularity。最后使用列排序一下,即從左到右排序。
* 找到連通域
connection (Balls, SingleBalls)
* 根據(jù)圓度來(lái)篩選連通域
select_shape (SingleBalls, IntermediateBalls, 'circularity', 'and', 0.85, 1.0)
* 根據(jù)列先后排序
sort_region (IntermediateBalls, FinalBalls, 'first_point', 'true', 'column')
其中圓度的計(jì)算方式在文檔中是這樣解釋的

說(shuō)了那么多其實(shí)挺好理解,就是計(jì)算區(qū)域的重心,然后找到區(qū)域內(nèi)距離重心最遠(yuǎn)的距離。假設(shè)連通域是標(biāo)準(zhǔn)的圓形,那么這個(gè)距離就是圓的半徑,通過(guò)上面的公式計(jì)算出來(lái)的結(jié)果近似為1。反之如果不是標(biāo)準(zhǔn)的圓形,就會(huì)趨向于0。
舉個(gè)例子。假如對(duì)下圖中紅色包圍的區(qū)域計(jì)算圓度,max就是距離重心最大的距離。公式中的分母就是通過(guò)max計(jì)算出的面積,在圖中就是黑色圓形包圍的區(qū)域。而公式中分子就是紅色包圍區(qū)域的面積。顯然這兩個(gè)區(qū)域的面積相差比較大,所以C比較小。反之,如果這個(gè)橢圓比較趨向于標(biāo)準(zhǔn)圓,C就趨向于1。
對(duì)一個(gè)橢圓求圓度
通過(guò)篩選連通域的圓度,可以把方形的焊點(diǎn)區(qū)域篩除。

最后再計(jì)算一下焊點(diǎn)的各個(gè)數(shù)據(jù)。因?yàn)檫B通域不是標(biāo)準(zhǔn)的圓形,這里是找到連通域的最小外包圓后來(lái)計(jì)算。
* 找到最小外包圓后計(jì)算各種數(shù)據(jù)
smallest_circle (FinalBalls, Row, Column, Radius)
NumBalls := |Radius|
Diameter := 2 * Radius
meanDiameter := sum(Diameter) / NumBalls
mimDiameter := min(Diameter)
最后結(jié)果如下

大功告成~
加注釋的程序
原程序的位置為:Blob分析 -> ball.hdev。
* ball.hdev: Inspection of Ball Bonding
* 識(shí)別PCB上的圓形焊點(diǎn)
*
dev_update_window ('off')
dev_close_window ()
dev_open_window (0, 0, 728, 512, 'black', WindowID)
read_image (Bond, 'die/die_03')
dev_display (Bond)
set_display_font (WindowID, 14, 'mono', 'true', 'false')
disp_continue_message (WindowID, 'black', 'true')
stop ()
threshold (Bond, Bright, 100, 255)
shape_trans (Bright, Die, 'rectangle2')
dev_set_color ('green')
dev_set_line_width (3)
dev_set_draw ('margin')
dev_display (Die)
disp_continue_message (WindowID, 'black', 'true')
stop ()
* 摳圖(選中ROI)
reduce_domain (Bond, Die, DieGrey)
threshold (DieGrey, Wires, 0, 50)
* 孔洞填充
fill_up_shape (Wires, WiresFilled, 'area', 1, 100)
dev_display (Bond)
dev_set_draw ('fill')
dev_set_color ('red')
dev_display (WiresFilled)
disp_continue_message (WindowID, 'black', 'true')
stop ()
opening_circle (WiresFilled, Balls, 15.5)
dev_set_color ('green')
dev_display (Balls)
disp_continue_message (WindowID, 'black', 'true')
stop ()
* 找到連通域
connection (Balls, SingleBalls)
* 根據(jù)圓度來(lái)篩選連通域
select_shape (SingleBalls, IntermediateBalls, 'circularity', 'and', 0.85, 1.0)
* 根據(jù)列先后
sort_region (IntermediateBalls, FinalBalls, 'first_point', 'true', 'column')
dev_display (Bond)
dev_set_colored (12)
dev_display (FinalBalls)
disp_continue_message (WindowID, 'black', 'true')
stop ()
* 找到最小外包圓后計(jì)算各種數(shù)據(jù)
smallest_circle (FinalBalls, Row, Column, Radius)
NumBalls := |Radius|
Diameter := 2 * Radius
meanDiameter := sum(Diameter) / NumBalls
mimDiameter := min(Diameter)
dev_display (Bond)
disp_circle (WindowID, Row, Column, Radius)
dev_set_color ('white')
for i := 1 to NumBalls by 1
if (fmod(i,2) == 1)
disp_message (WindowID, 'D: ' + Diameter[i - 1], 'image', Row[i - 1] - 2.7 * Radius[i - 1], max([Column[i - 1] - 60,0]), 'white', 'false')
else
disp_message (WindowID, 'D: ' + Diameter[i - 1], 'image', Row[i - 1] + 1.2 * Radius[i - 1], max([Column[i - 1] - 60,0]), 'white', 'false')
endif
endfor
* dump_window (WindowID, 'tiff_rgb', './ball')
dev_set_color ('green')
dev_update_window ('on')
disp_continue_message (WindowID, 'black', 'true')
