Halcon視覺入門芯片識別


Halcon視覺入門芯片識別

需求

有如下圖的一個擺盤,擺盤的方格中擺放芯片,一個格子中只放一個,我們需要知道每個方格中是否有芯片去指導我們將芯片放到空的方格中。

image

分析

通過圖片分析得出

  1. 我們感興趣的區域在中間,每個方格大小類似

  2. 芯片大小相同

  3. 方格是白色的

  4. 方格有100個,方格外的不識別

解決問題的思路

  1. 建立ROI關注每一個方格

  2. 需要提取方格的特征

  3. 單獨分析每一個方格,如果方格中存在芯片(通過特征分析芯片是否存在),則標注出來,如果不存在,標注為紅色,代表是空的,可以擺放芯片。

在編寫代碼之前,大體思路出來了,需要的是嘗試各種參數得到我們想要的結果。

代碼如下:


dev_close_window ()
dev_open_window (0, 0, 1200, 850, 'black', WindowHandle) 
dev_clear_window() 
* read_image (Image, 'D:/temp/chips/20220110112001.jpg')  
* read_image (Image, 'D:/temp/chips/20220110112121.jpg')  
* read_image (Image, 'D:/temp/chips/20220110112137.jpg')
 read_image (Image, 'D:/temp/chips/Image_20211227154621354.bmp') 

*使用canny算子提取亞像素邊界
edges_sub_pix (Image, Edges, 'canny', 10, 20, 40) 
*根據邊長和面積提取xld
select_shape_xld (Edges, SelectedXLD, ['rect2_len1','area'], 'and', [70,18000], [100,33000]) 
*根據xld繪出區域
gen_region_contour_xld (SelectedXLD, Region, 'margin') 
*根據區域面積提取芯片盒子
select_shape (Region, ChipsImage, ['width','height'], \
'and', [155,155], [170,170]) 
*合並區域
union1 (ChipsImage, RegionUnion)
*填充區域
fill_up (RegionUnion, ROI_0)
*根據聯合區域裁剪感興趣區域
reduce_domain (Image, ROI_0, ImageReduced)
*聯通分割區域
connection (ImageReduced, ConnectedRegions) 
*將每個芯片方格轉換成標准矩形
shape_trans (ConnectedRegions, RegionTransRect, 'rectangle1')
*排序
sort_region (RegionTransRect, SortedRegions, 'character', 'true', 'row') 
*總孔位數量
count_obj (SortedRegions, Number) 
*結果數組,0 代表沒有 1代表有芯片
result:= [] 
*空位的數量
EmptyCount :=0 
dev_display (Image) 
for Index := 1 to Number by 1 
  * 找到索引對應的區域
   select_obj (SortedRegions, ALL_ROI_OF_SINGLES, Index)
   *裁剪區域
   reduce_domain (Image, ALL_ROI_OF_SINGLES, ChipBoxRegion)   
   *動態閾值分割
   binary_threshold (ChipBoxRegion, ChipRegion, 'max_separability', 'dark', UsedThreshold)
   *反向選擇
   difference (ChipBoxRegion, ChipRegion, ChipWithLine)  
   *聯通區域
   connection (ChipWithLine, ConnectedRegions1) 
   *根據面積特征篩選區域
   select_shape (ConnectedRegions1, SelectedRegions, 'area', 'and', 1000.59, 3001.78)
   *腐蝕
   erosion_circle (SelectedRegions, ChipErosion, 2)    
   *再次通過面積篩選  
   select_shape (ChipErosion, ChipInside, 'area', 'and', 1000, 3000)
   *腐蝕
   erosion_circle (ChipInside, ChipErosion1, 2) 
   *計算賽選個數
   count_obj (ChipErosion1, chipExists) 
  
   if (chipExists == 0) 
       *判斷不存在芯片的邏輯
        EmptyCount := EmptyCount +1
        *顯示用
        dev_update_on()
        dev_set_color ('red')
        dev_set_draw ('fill')
        dev_display (ALL_ROI_OF_SINGLES)   
        dev_update_off()
   endif  
   if(chipExists == 1) 
       *判斷有芯片的邏輯 
       
       *顯示用
        dev_update_on()
        dev_set_color ('green')
        dev_set_draw ('fill') 
        shape_trans (ChipErosion1, RegionTrans, 'rectangle2')
        dev_display (RegionTrans)  
        dev_update_off()
   endif
   result[Index-1] := chipExists  
endfor  
disp_message (WindowHandle, ' Holes Number:'+Number + ' , Exists Count:' + (Number - EmptyCount) , 'window', 12, 12, 'black', 'true')  

下面開始分析代碼


dev_close_window ()
dev_open_window (0, 0, 1200, 850, 'black', WindowHandle) 
dev_clear_window() 
* read_image (Image, 'D:/temp/chips/20220110112001.jpg')  
* read_image (Image, 'D:/temp/chips/20220110112121.jpg')  
* read_image (Image, 'D:/temp/chips/20220110112137.jpg')
 read_image (Image, 'D:/temp/chips/Image_20211227154621354.bmp') 

這段代碼,看算子名稱就知道意思。關閉窗口;打開窗口,然后清空窗口;最后讀取一張圖片。讀取的圖片就是上圖展示的圖片。

*使用canny算子提取亞像素邊界
edges_sub_pix (Image, Edges, 'canny', 10, 20, 40) 
*根據邊長和面積提取xld
select_shape_xld (Edges, SelectedXLD, ['rect2_len1','area'], 'and', [70,18000], [100,33000]) 

這里是有兩個概念 亞像素xld

亞像素 因為圖片的最小單位是像素,亞像素是比像素還小的顯示方式,但只是在顯示上做的區分,並不是可以精確到亞像素。

xld 可以理解成輪廓

edges_sub_pix (Image, Edges, 'canny', 10, 20, 40)

Image 是輸入;Edges 是輸出, canny,10,20,40 是控制參數

為什么是這4個控制參數,可以參考F1

這里 只有 10 是 經過調整的參數,其他參數是默認值,數值越小精細度越高,細節也就越多,需要處理的數據也就越多。當它是1 的時候如圖:
image

當它是10的時候如圖:
image

當參數是10的時候,我們需要的輪廓已經出來了,然后通過邊長(rect2_len1)和面積(area)過濾出我們需要的區域,如圖:

image

可以看到,干擾不多了,接下來我們要將xld 繪制成區域,用到算子 gen_region_contour_xld

*根據xld繪出區域
gen_region_contour_xld (SelectedXLD, Region, 'margin') 

這里可以看到,上一步的結果,是這一步的輸入,這是Halcon編程的基本套路。第二個參數是輸出參數,也就是我們得到的區域,第三個參數是繪制方式,這里用到了margin 當然也可以用fill,得到的結果是這樣的:

image

然后再根據面積過濾掉不要的對象,說的是面積,實際上用到的是 寬(width),高(height)

*根據區域面積提取芯片盒子
select_shape (Region, ChipsImage, ['width','height'], \
'and', [155,155], [170,170]) 

這里的參數 155-170 是通過特征窗口得到的,如圖:

image

然后就是這樣的結果了,如圖:

image

*合並區域
union1 (ChipsImage, RegionUnion)
*填充區域
fill_up (RegionUnion, ROI_0)
*根據聯合區域裁剪感興趣區域
reduce_domain (Image, ROI_0, ImageReduced)
*聯通分割區域
connection (ImageReduced, ConnectedRegions) 
*將每個芯片方格轉換成標准矩形
shape_trans (ConnectedRegions, RegionTransRect, 'rectangle1')
*排序
sort_region (RegionTransRect, SortedRegions, 'character', 'true', 'row') 
*總孔位數量
count_obj (SortedRegions, Number) 

這段代碼就很好理解了,合並區域(union1),為什么要合並呢,因為這些區域是分散的,我們需要建立一個感興趣區域,然后對它進行裁剪,所以需要將他們合並在一起。合並后我做了一個填充也就是 (fill_up),填充后是這樣的:

image

裁剪( reduce_domain (Image, ROI_0, ImageReduced) )后是這樣的:

image

接下來就是連通區域(connection),將他們分割,然后 轉成標准矩形(shape_trans),排序(sort_region),統計個數(count_obj),這樣我們就得到了每個方格。

這里說一下排序,排序是為了讓它從左到右從上到下方便我們遍歷。

*結果數組,0 代表沒有 1代表有芯片
result:= [] 
*空位的數量
EmptyCount :=0 

這里定義了一個數組存放結果,定義了一個遍歷存放空位。接下來就是開始遍歷分析每一個方格對象。

這里說一下,這個語法跟pascal語法很像,之前有做過Delphi項目,所以這個語法我也沒有專門學過,做到哪里查到哪里,有不足的地方還請指正。

for Index := 1 to Number by 1 
  * 找到索引對應的區域
   select_obj (SortedRegions, ALL_ROI_OF_SINGLES, Index)
   *裁剪區域
   reduce_domain (Image, ALL_ROI_OF_SINGLES, ChipBoxRegion)   
   *動態閾值分割
   binary_threshold (ChipBoxRegion, ChipRegion, 'max_separability', 'dark', UsedThreshold)
   *反向選擇
   difference (ChipBoxRegion, ChipRegion, ChipWithLine)  
   *聯通區域
   connection (ChipWithLine, ConnectedRegions1) 
   *根據面積特征篩選區域
   select_shape (ConnectedRegions1, SelectedRegions, 'area', 'and', 1000.59, 3001.78)
   *腐蝕
   erosion_circle (SelectedRegions, ChipErosion, 2)    
   *再次通過面積篩選  
   select_shape (ChipErosion, ChipInside, 'area', 'and', 1000, 3000)
   *腐蝕
   erosion_circle (ChipInside, ChipErosion1, 2) 
   *計算賽選個數
   count_obj (ChipErosion1, chipExists) 
  
   if (chipExists == 0) 
       *判斷不存在芯片的邏輯
        EmptyCount := EmptyCount +1 
   endif  
   if(chipExists == 1) 
       *判斷有芯片的邏輯  
   endif
   result[Index-1] := chipExists  
endfor  

這里說幾個算子 select_obj 可以選擇某個區域,select_obj (SortedRegions, ALL_ROI_OF_SINGLES, Index)
第一個參數是所有區域的集合,第二個參數是輸出的區域,也就是我們通過索引得到的區域,第三個參數是索引

這里是索引是從1開始的,要格外注意

接下來就繼續裁剪,根據每個小方格,繼續裁剪,如圖:

image

因為動態閾值分割的結果是這樣的,如圖:

image

它選擇了我們不要的內容,所以需要求反。用到了 difference (ChipBoxRegion, ChipRegion, ChipWithLine) 。這里第一個參數是整體,第二個參數是閾值分割的結果,第三個參數是求差之后的結果,也就是我們需要的結果,如圖:

image

此時,按照慣例,處理邊緣的干擾,如果得到的區域是有內容的,說明是有芯片的,如果沒有內容,說明沒有放芯片。

   *聯通區域
   connection (ChipWithLine, ConnectedRegions1) 
   *根據面積特征篩選區域
   select_shape (ConnectedRegions1, SelectedRegions, 'area', 'and', 1000.59, 3001.78)
   *腐蝕
   erosion_circle (SelectedRegions, ChipErosion, 2)    
   *再次通過面積篩選  
   select_shape (ChipErosion, ChipInside, 'area', 'and', 1000, 3000)
   *腐蝕
   erosion_circle (ChipInside, ChipErosion1, 2) 
   *計算賽選個數
   count_obj (ChipErosion1, chipExists) 

連通區域,通過面積過濾,然后 腐蝕,腐蝕的目的是去掉周邊的干擾。這個數值 2 也是經過多次嘗試得到的。

然后再次通過面積篩選,去掉干擾。繼續腐蝕,也是為了去掉干擾。這里的邏輯會根據實際情況發生變化。

最后計數( count_obj (ChipErosion1, chipExists) )。如果chipExists ==1 說明有芯片,否則就是沒有芯片。

然后根據 chipExists的值進行判斷處理即可。我這里的處理方式是標注,如圖:

image

紅色代表是空的,如果不是空的,我們將芯片的區域繪制成綠色。

總結

在編程的過程中,感受到視覺識別是一門多方面配合的工作,如果光線情況比較好,精度高很多時候可以讓我們節省很多處理步驟。所以,視覺識別不單單是編程的問題。如果能在前期將光打好,后期可以節省很多事情,提高處理效率。

最后如果本文有不對的地方,歡迎指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM