
做機器視覺的朋友應(yīng)該都遇到過“在線測量尺寸”這類需求。今天我把Halcon官方例程里一個很典型的項目拿出來拆一拆——check_fish_stick_dimension翻譯過來就是測量生魚條的大小。這個例程解決的是食品加工產(chǎn)線上的一個真實問題魚條切出來之后長度和寬度是否滿足包裝規(guī)格。超長、超寬、斷條、粘連都會直接影響后續(xù)裝罐、稱重和品控判定。別小看這個例程。它雖然只處理“一條魚條”的測量但內(nèi)部幾乎覆蓋了機器視覺測量的所有基礎(chǔ)環(huán)節(jié)圖像分割、連通域分析、形態(tài)學(xué)處理、亞像素邊緣提取、矩形擬合、標(biāo)定以及公差判斷。你把這一套吃透很多二維尺寸測量項目都可以套用同樣的套路去開發(fā)從金屬沖壓件、木地板條到藥板、管材外徑思路完全一致。這個例程適合誰來讀如果你是剛接觸視覺測量、想找一份能落地參考代碼的新手這個例程是一個很理想的起步項目。如果你想在現(xiàn)有產(chǎn)線里快速驗證“視覺能不能替代卡尺抽檢”把例程思路遷移過去的速度也很快。我下面會從業(yè)務(wù)需求、技術(shù)選型、算子細(xì)節(jié)到調(diào)試踩坑完整拆一遍。1. 例程背后的真實業(yè)務(wù)需求1.1 魚條尺寸檢測解決什么問題先說業(yè)務(wù)邏輯。魚條生產(chǎn)出來要裝袋或裝罐如果尺寸參差不齊重量就會飄忽不定。舉個例子一包標(biāo)注凈含量454克如果魚條偏大實際重量可能超到480克廠家的成本就高了如果偏小裝到450克市場監(jiān)管抽檢時又面臨短斤缺兩的風(fēng)險。因此魚條在切割完成后生產(chǎn)線上必須有一個環(huán)節(jié)對尺寸進行抽檢或全檢。這個例程本身是用靜態(tài)圖像來做測量模擬的就是抽檢工位的視覺系統(tǒng)一條魚條被送到相機視野內(nèi)拍一張圖計算機馬上量出長度、寬度與標(biāo)準(zhǔn)值對比輸出OK或NG。在實際產(chǎn)線上這張圖往往來自光電傳感器觸發(fā)的高速相機而非手工拍攝但核心算法邏輯完全一樣。除此之外還有一層潛在需求數(shù)據(jù)記錄。人工量尺寸只能記錄在紙面上而視覺系統(tǒng)可以輸出每個周期內(nèi)所有魚條長度、寬度的統(tǒng)計數(shù)據(jù)比如CPK、均值、極差。生產(chǎn)管理人員可以直接利用這些數(shù)據(jù)調(diào)整切割機的參數(shù)做閉環(huán)控制這才是視覺測量價值的最大化。1.2 人工卡尺抽檢的痛點很多人第一反應(yīng)是魚條尺寸難道不能用尺量嗎能。但你產(chǎn)線一分鐘過60條魚條質(zhì)檢員要保持什么速度才能全檢就算全檢連續(xù)測量半小時后眼睛和手腕都會疲勞讀數(shù)誤差會增大。更重要的是人工測量是非實時、非連續(xù)的無法做到產(chǎn)品全檢只能事后抽檢發(fā)現(xiàn)問題時已經(jīng)產(chǎn)生了一批不合格品。從成本上看一個專職工位一年的用工成本可能比一套視覺檢測設(shè)備的折舊成本還高。從穩(wěn)定性看人工測量的標(biāo)準(zhǔn)并不統(tǒng)一不同質(zhì)檢員量出來的結(jié)果可以差到1到2毫米。視覺測量則一次性標(biāo)定好基準(zhǔn)誤差對所有人都是恒定的。1.3 到底要測哪些尺寸從例程名“check dimension”可以看出核心是測量尺寸。具體到生魚條主要測三個量長度、寬度、面積。長度和寬度不是簡單對圖像左右上下比劃而是先找到魚條的主軸方向再沿主軸及其垂直方向取外接尺寸。Halcon中常用的做法是擬合一個最小外接矩形矩形的長邊對應(yīng)魚條長度短邊對應(yīng)寬度。再補一個細(xì)節(jié)魚條兩端不一定是平齊的可能是斜切的這時候最小外接矩形依然能給出最高效的包絡(luò)尺寸。但如果你要的是“切面到切面”的精確長度就得在兩端各建一條測量線用測量算子拉距離。例程為了通用性用的是矩形擬合方案。公差判斷原則是長度在[L_min, L_max]范圍內(nèi)、寬度在[W_min, W_max]范圍內(nèi)同時面積大于某個下限三個條件都滿足才算合格。為什么面積也要管因為魚條如果斷成兩截或者邊緣殘缺長度和寬度可能還在范圍內(nèi)面積卻明顯偏小面積可以兜底。2. 整體方案架構(gòu)與技術(shù)選型2.1 從圖像到測量結(jié)果的完整處理管線先梳理管線。Halcon例程的經(jīng)典結(jié)構(gòu)是圖像讀取 → 預(yù)處理分割 → 連通域分析 → 目標(biāo)篩選 → 形態(tài)學(xué)處理 → 輪廓提取 → 矩形擬合 → 像素尺寸提取 → 物理標(biāo)定 → 公差判斷 → 結(jié)果展示我說的“讀取”在你本地復(fù)現(xiàn)時可以把read_image換成grab_image從相機實時采圖。預(yù)處理分割階段最常用的就是閾值分割。因為魚條顏色與背景形成明顯對比時一個threshold就能得到目標(biāo)區(qū)域。但真實產(chǎn)線里照明不穩(wěn)定、污漬、水汽都會影響灰度分布所以例程后續(xù)還用了區(qū)域形態(tài)學(xué)算子做“修補”。目標(biāo)篩選階段用select_shape。一張圖可能拍到多條魚條也可能有魚條碎片、水漬形成的偽區(qū)域通過面積、長度、寬度等幾何特征把需要的魚條挑出來。輪廓和測量階段用edges_sub_pix提取亞像素級別邊緣再用fit_rectangle2_contour_xld擬合矩形。這兩個算子配合測量的重復(fù)精度可以到達亞像素層。最后通過標(biāo)定將像素尺寸換算成毫米。Halcon里最常用的是pixels_to_plm和plm_to_pixels。這套算子的底層是建立一套圖像坐標(biāo)到物理坐標(biāo)的映射關(guān)系具體在后面詳解。2.2 為什么選Halcon而不是從零寫算法在尺寸測量這個領(lǐng)域我見過很多人一上來就想自己寫C加OpenCV實現(xiàn)。也不是不行但對于產(chǎn)線級項目成熟的視覺庫在算法穩(wěn)定性、邊緣提取魯棒性、標(biāo)定工具鏈和調(diào)試體驗上優(yōu)勢非常明顯。Halcon的算子經(jīng)過大量工業(yè)項目驗證同樣的邊緣提取自己寫的Canny參數(shù)調(diào)到崩潰換Halcon算子可能參數(shù)只需要兩個。Halcon的另一個優(yōu)勢是HDevelop交互開發(fā)環(huán)境。你可以一邊調(diào)閾值一邊看區(qū)域變化這種“所見即所得”的調(diào)試方式在項目初期摸清圖像特征時特別高效。例程本身是用HDevelop腳本寫的稍加修改就能打包成C/C#工程從原型到上線路徑很成熟。當(dāng)然選型不是唯一的。如果你所在團隊對開源棧有硬性要求OpenCV也完全能做只是需要投入更多時間做調(diào)參和邊界情況處理。我的建議是項目周期緊、精度要求高、交付要快的優(yōu)先考慮商業(yè)庫做研究驗證或預(yù)算受限再考慮開源方案。2.3 硬件側(cè)的幾個關(guān)鍵匹配點算法只是系統(tǒng)的一半。這里必須提醒一下硬件匹配。相機分辨率決定測量精度。假設(shè)視野是200毫米要求檢測精度±0.5毫米那么每個像素對應(yīng)的物理尺寸至少是0.5毫米實際上要留2到3倍余量也就是0.2毫米左右對應(yīng)水平方向至少1000像素。但這只是最低標(biāo)準(zhǔn)如果還要測量寬度并保證重復(fù)性建議按更高密度選型。500萬像素級別相機在這個視野下比較保險。鏡頭選擇優(yōu)先低畸變定焦鏡頭?;儠绊戇吘壩恢玫恼鎸嵍热绻R頭畸變大標(biāo)定模型再精確也會殘留系統(tǒng)誤差。照明方式上測量外輪廓的項目首選背光源也就是把光源放在被測物背面讓魚條在圖像里形成黑色剪影。背光得到的邊緣對比度極高閾值分割幾乎不會出意外。如果因為結(jié)構(gòu)限制只能用環(huán)形光源或條形光源就要做好灰度不均的準(zhǔn)備后面閾值參數(shù)和形態(tài)學(xué)參數(shù)都要跟著調(diào)整。這是很多新人一上來就踩的坑拿環(huán)形光拍出來的圖邊緣一會亮一會暗閾值怎么調(diào)都穩(wěn)定不下來。3. 核心算子解析與關(guān)鍵參數(shù)調(diào)優(yōu)3.1 閾值分割從threshold到動態(tài)閾值的演進例程通常會用類似下面的方式把魚條從背景中分出來threshold (Image, Regions, 80, 255)理解一下這個算子把灰度值介于80到255之間的所有像素劃入?yún)^(qū)域。80這個下限是怎么來的在典型的背光照明下魚條區(qū)域的灰度一般高于背景而且灰度分布比較集中。你可以打開灰度直方圖找到波谷位置作為閾值下限。但是“80”這種固定值在產(chǎn)線調(diào)試?yán)锎嬖陔[患光照波動或不同批次原料顏色改變都可能讓閾值失效。所以例程給的是思路實際部署時我喜歡改成動態(tài)閾值或自動閾值。Halcon里可以用auto_threshold或者采用每個ROI內(nèi)求均值加減倍數(shù)的策略讓閾值跟著圖像特征走。另外強調(diào)一點閾值分割只做一步就完事太理想化了。魚條表面可能有紋理暗斑分割出來的區(qū)域中間會帶小孔魚條邊緣可能有毛刺區(qū)域邊界會坑坑洼洼。這些都要交給后面的形態(tài)學(xué)算子收拾。3.2 連通域分析與目標(biāo)選擇分割完成后圖像里的目標(biāo)可能不止一個。connection算子的作用是把連通的像素區(qū)域劃分為一個個獨立的區(qū)域?qū)ο笙喈?dāng)于給每個連通塊發(fā)一個編號。隨后是select_shapeselect_shape (ConnectedRegions, Selected, [area, width, height], and, [5000, 20, 30], [999999, 999, 999])這里選了面積、寬度、高度三個特征。面積下限5000像素是為了去掉小噪點寬度和高度區(qū)間把太細(xì)的碎片排除掉。width和height是按照區(qū)域主軸方向的外接矩形尺寸不是簡單軸向尺寸所以對于傾斜的魚條也一樣有效。有一個調(diào)試技巧select_shape 參數(shù)不要在代碼里盲調(diào)先用 HDevelop 的“特征直方圖”交互查看每個特征值分布確定閾值后再寫死到代碼里。這樣選出來的參數(shù)有依據(jù)不會拍腦袋。3.3 形態(tài)學(xué)處理補洞和去毛刺分割得到的區(qū)域通常不夠“干凈”。魚條表面紋理可能產(chǎn)生孔洞影響面積特征邊緣毛刺會干擾后續(xù)矩形擬合。Halcon里常用fill_up把區(qū)域內(nèi)部孔洞填掉再用closing_circle做閉運算把邊緣細(xì)小凹陷磨平。fill_up (Selected, RegionFillUp) closing_circle (RegionFillUp, RegionClosing, 3.5)closing_circle 的半徑3.5是個經(jīng)驗值具體要看毛刺寬度。半徑太小毛刺壓不干凈半徑太大會把魚條真實輪廓磨圓導(dǎo)致后續(xù)測量偏小。這個值要在“去噪”和“保真”之間取平衡。這里順便說一個容易忽略的點形態(tài)學(xué)操作會改變區(qū)域尺寸。如果后續(xù)需要測量區(qū)域的面積閉運算后面積會有小幅變化所以要謹(jǐn)慎控制結(jié)構(gòu)元素尺寸。對于嚴(yán)格測量項目我更傾向于只做fill_up不去做closing寧可讓邊緣毛糙一點也要保證幾何尺寸不失真。3.4 亞像素邊緣提取與矩形擬合區(qū)域本身是像素級的直接量區(qū)域尺寸精度只能到1個像素。為了更精確要從區(qū)域邊界生成亞像素輪廓gen_contour_region_xld (RegionClosing, Contours, border)從區(qū)域輪廓XLD已經(jīng)可以做很多測量。但更專業(yè)的做法是直接在灰度圖上提取亞像素邊緣edges_sub_pix (ImageReduced, Edges, canny, 1.5, 20, 40)Canny算子的參數(shù)里1.5是高斯平滑系數(shù)20和40分別對應(yīng)滯后閾值的低閾值和高閾值。低閾值20用于弱邊緣跟蹤高閾值40用于確定強邊緣起點。這個組合在大多數(shù)測量場景是穩(wěn)定起點再根據(jù)實際邊緣噪聲調(diào)整。拿到邊緣后用fit_rectangle2_contour_xld擬合外形矩形fit_rectangle2_contour_xld (Edges, regression, -1, 0, 0, 3, 2, Row, Column, Phi, Length1, Length2)這個算子輸出矩形的中心坐標(biāo)Row、Column旋轉(zhuǎn)角度Phi半長Length1和半寬Length2。注意Length1一定是長邊的一半。因此魚條長度就是2 * Length1寬度是2 * Length2。為什么用擬合而不是直接用區(qū)域的最小外接矩形因為亞像素邊緣經(jīng)過擬合后精度遠(yuǎn)高于像素級區(qū)域角度和寬度的重復(fù)性更好。3.5 像素到毫米標(biāo)定與pixels_to_plm測量最終要輸出物理尺寸必須做標(biāo)定。Halcon中最常用的標(biāo)定方式是用標(biāo)定板。通過find_calib_object找到標(biāo)定板特征點然后用get_calib_data提取相機內(nèi)參和畸變系數(shù)最后建立像素坐標(biāo)與物理坐標(biāo)的映射。例程里簡化了標(biāo)定流程直接建立了一個位置無關(guān)的像素物理映射pixels_to_plm (Row, Column, 0.05, 0.05, 0, 0, RowPhysical, ColumnPhysical)0.05代表每個像素對應(yīng)0.05毫米也就是當(dāng)標(biāo)定結(jié)果確認(rèn)1個像素等于0.05毫米時填這個值。這個映射只含比例和旋轉(zhuǎn)不包含畸變校正。它是快速測量的簡化方案適合視野小、鏡頭畸變低、相機與物體垂直的場景。如果相機有透視角度或鏡頭畸變明顯必須用完整的相機標(biāo)定模型。否則你會發(fā)現(xiàn)魚條放在視野中間和放在邊緣測量結(jié)果會不一樣這就是畸變造成的。標(biāo)定的關(guān)鍵不只是“做了”而是要驗證把標(biāo)準(zhǔn)量塊放在視野多個位置分別測量結(jié)果都要落在標(biāo)稱值公差內(nèi)。4. 實操過程與核心代碼逐段解讀4.1 讀圖與初始化顯示以下是例程思路的完整落地你可以按順序?qū)懭際Develop驗證* 讀取圖像這里以靜態(tài)圖模擬相機抓拍 read_image (Image, fish_stick_01) get_image_size (Image, Width, Height) * 初始化窗口 dev_close_window () dev_open_window (0, 0, Width/2, Height/2, black, WindowHandle) dev_display (Image)read_image后面跟的路徑要換成你自己的樣本路徑。如果接相機用grab_image替換并且建議在循環(huán)里連續(xù)取幀。4.2 目標(biāo)區(qū)域分割* 閾值分割灰度80到255 threshold (Image, Regions, 80, 255) * 分成獨立連通區(qū)域 connection (Regions, ConnectedRegions) * 按幾何特征篩選目標(biāo)魚條 select_shape (ConnectedRegions, SelectedRegions, [area, width, height], and, [5000, 20, 30], [999999, 999, 999]) * 補洞 fill_up (SelectedRegions, RegionFillUp)如果你拍的魚條顏色較淺背景是深色傳送帶閾值方向就反過來了閾值應(yīng)寫成0到150。判斷方向的標(biāo)準(zhǔn)很簡單目標(biāo)區(qū)域灰度高于背景就用高閾值低于背景就用低閾值甚至可以用灰度直方圖一眼確認(rèn)。4.3 提取亞像素邊緣并擬合矩形* 將區(qū)域縮小到圖像上減少計算量 reduce_domain (Image, RegionFillUp, ImageReduced) * 亞像素邊緣提取 edges_sub_pix (ImageReduced, Edges, canny, 1.5, 20, 40) * 擬合矩形 fit_rectangle2_contour_xld (Edges, regression, -1, 0, 0, 3, 2, Row, Column, Phi, Length1, Length2)這一步是測量的核心。fit_rectangle2_contour_xld 的第三個參數(shù) -1 表示所有邊緣點都參與擬合最后兩個參數(shù)2和3控制迭代次數(shù)和端點抑制在某些邊緣缺損場景可以調(diào)整。例如魚條中間有污漬產(chǎn)生斷邊可以適當(dāng)提高迭代參數(shù)讓擬合更穩(wěn)健。4.4 計算物理尺寸并判斷合格* 像素尺寸轉(zhuǎn)物理尺寸 PixelLength : 2 * Length1 PixelWidth : 2 * Length2 pixels_to_plm (PixelLength, PixelWidth, 0.05, 0.05, 0, 0, PhysicalLength, PhysicalWidth) * 設(shè)定公差 LMin : 90.0 LMax : 110.0 WMin : 8.0 WMax : 12.0 * 判斷 if (PhysicalLength LMin and PhysicalLength LMax and PhysicalWidth WMin and PhysicalWidth WMax) Result : OK else Result : NG endif這里的90到110毫米、8到12毫米只是示例規(guī)范實際以產(chǎn)線工藝文件為準(zhǔn)。判定邏輯可以擴展比如面積也要參與判定或者輸出超差偏差值讓執(zhí)行機構(gòu)做分選。要注意 Halcon 的 if 條件寫法是if (條件)不是if 條件小括號必不能少。4.5 顯示與數(shù)據(jù)輸出dev_display (Image) disp_message (WindowHandle, Length: PhysicalLength$5.2f mm, Width: PhysicalWidth$5.2f mm, Result: Result, window, 12, 12, black, true)結(jié)果顯示用disp_message把測量數(shù)值動態(tài)拼到字符串里。這里的格式化寫法PhysicalLength$5.2f是HDevelop常用的數(shù)值轉(zhuǎn)字符串方式。實際項目中這段輸出會替換成寫數(shù)據(jù)庫、發(fā)PLC信號或者存CSV基本邏輯相同。5. 現(xiàn)場調(diào)試中那些不得不說的坑5.1 魚條粘連導(dǎo)致漏檢產(chǎn)線輸送過程中魚條常常挨在一起閾值分割得到的是連成一片的大區(qū)域select_shape篩選時會把兩條魚并成一條長度直接翻倍。排查對策先從形態(tài)學(xué)分離用opening_rectangle1沿水平方向破開連接橋或者用watersheds做分水嶺分割。如果魚條朝向基本固定破開方向的矩形結(jié)構(gòu)選寬度1到3像素效果立竿見影。5.2 水漬和反光讓灰度閾值失效冷凍魚條表面常有一層冰水膜在光源下形成鏡面反光灰度值局部接近白色把魚條區(qū)域直接“打斷”成兩截面積特征變小甚至被判為NG。對策是改用偏振光源或偏振片消除反光算法側(cè)可把閾值模式改為“像素值分段”把反光區(qū)域重新合并回魚條區(qū)域。測試時一定要準(zhǔn)備帶水漬的樣品不要只測干燥狀態(tài)否則現(xiàn)場一定翻車。5.3 標(biāo)定誤差比想象中更容易遇到標(biāo)定板沒放平、相機傾斜、標(biāo)定圖像拍糊都會引入標(biāo)定誤差。我見過量長度偏了0.8毫米的項目最后發(fā)現(xiàn)是標(biāo)定板表面有輕微彎曲物理坐標(biāo)系本身就不準(zhǔn)。標(biāo)定時要拍多張不同姿態(tài)的圖算子會自動優(yōu)化內(nèi)參和外參減少隨機誤差。如果產(chǎn)線環(huán)境不允許放標(biāo)定板可以用已知尺寸的標(biāo)準(zhǔn)塊在視野內(nèi)多個位置比對驗證至少每季度復(fù)校一次。5.4 速度與精度的取舍在線檢測對節(jié)拍有要求而精度又跟圖像分辨率正相關(guān)兩者天然矛盾。我的建議先算好預(yù)算精度再定相機分辨率不要上來就上最高分辨率。分辨率越高單幀數(shù)據(jù)量越大處理耗時越長成本也越高。如果產(chǎn)線節(jié)拍是每分鐘60條單條處理時間必須小于1秒500萬像素級別的圖在普通工控機上跑這套流程通常沒問題但要留足CPU余量。5.5 一個容易被忽略的細(xì)節(jié)邊緣毛刺魚條切面往往有碎肉和纖維毛刺提取邊緣時會出現(xiàn)大量小尖刺參與擬合導(dǎo)致矩形擬合時角度偏移測量長度時好時壞。解決思路是先用smooth_contours_xld對輪廓做平滑或者選用擬合模式robust替代regression讓離群點權(quán)重降低。這一條在測量任何切割類食品時都非常管用。最后再分享一個實用習(xí)慣驗收視覺測量項目時我都會準(zhǔn)備三種樣品——標(biāo)準(zhǔn)合格品、尺寸臨界品、帶表面干擾的劣質(zhì)品分別測幾十次統(tǒng)計重復(fù)性和誤判率。這套流程跑通比任何紙上參數(shù)都管用。這個習(xí)慣大家可以直接帶走拿到任何一個新例程都適用。