線替換邏輯)
簡介本資源面向計算機視覺與工業(yè)自動化方向的開發(fā)者及學生提供一套基于 OpenCV 與深度學習實現(xiàn)富士康金屬件自動化尺寸測量的完整項目源碼與配套數(shù)據(jù)。項目核心思路是先定位目標位置再統(tǒng)計其像素面積結合比例尺換算實際尺寸涵蓋圖像匹配、特征提取、角度測量等典型難點并附有 0/4/7 號位置的識別測量結果供對照驗證。壓縮包共 88 個文件以 44 個 png 與 9 個 jpg 圖像樣本、20 個 py 源碼腳本為主另含 json 配置、mov/mp4 演示視頻、md 說明文檔及 ipynb 實驗筆記整體約 47.56MB目錄按 code、data、docs、dataset 等模塊組織便于按流程檢索。目前已有 147 人學習下載。讀者可獲取從圖像預處理、目標定位到尺寸換算的完整算法鏈路以及針對小尺寸實物、工業(yè)相機放大受限等問題的排錯思路與實驗記錄適合作為工業(yè)視覺測量項目的參考方案。1. 富士康金屬件尺寸測量從卡尺到相機的產(chǎn)線替換邏輯一條手機中框從沖壓線下來質(zhì)檢工位上的老手會拿數(shù)顯卡尺量長寬、用塞尺測段差、再拿投影儀抽檢幾個關鍵孔位。這套流程單件耗時幾十秒抽檢率上不去漏檢的件流到組裝段才被發(fā)現(xiàn)返工成本直接翻倍?;谟嬎銠C視覺的富士康金屬件自動化尺寸測量算法要解決的就是把這道工位從「人眼加卡尺」換成「相機加算法」——用工業(yè)相機拍一張金屬件正投影圖通過 OpenCV 做邊緣提取和亞像素定位把像素坐標換算成毫米輸出長、寬、孔徑、孔距、段差這些尺寸和公差帶比對后判定 OK/NG。這套方案適合三類人一是做 3C 結構件質(zhì)檢的自動化工程師手上有產(chǎn)線但抽檢率卡在瓶頸二是計算機視覺方向的學生或轉(zhuǎn)行者想找一個有明確物理意義、能跑通全流程的實戰(zhàn)項目而不是只調(diào)分類模型三是產(chǎn)線設備集成商需要一套可復現(xiàn)的 Python 尺寸測量基線再往 Halcon 或?qū)S脺y量機方向做精度升級。它不追求微米級計量室精度目標是在產(chǎn)線節(jié)拍內(nèi)做到 ±0.02mm 級別的重復性把全檢變成可能。2. 像素到毫米尺寸測量算法的四個核心環(huán)節(jié)2.1 標定先行為什么沒有標定就沒有尺寸視覺測量的第一性原理是相機看到的是像素不是毫米。像素和毫米之間的換算關系必須通過標定建立。最常見的做法是使用棋盤格標定板用cv2.findChessboardCorners找到角點再用cv2.calibrateCamera解出內(nèi)參矩陣和畸變系數(shù)。內(nèi)參矩陣里的焦距fx, fy和主點cx, cy決定了像素與物理尺寸的映射比例畸變系數(shù)則用來校正鏡頭帶來的桶形或枕形變形。對于產(chǎn)線測量我一般會做兩步標定先做相機內(nèi)參標定消除鏡頭畸變再在測量平面上放一個已知尺寸的標準件比如 50mm 量塊拍圖后計算「像素/毫米」比例這個比例就是后續(xù)所有尺寸換算的基準。如果只做內(nèi)參標定不做平面比例標定測量結果會隨工件在視野中的位置漂移這是新手最容易翻車的地方。import cv2 import numpy as np # 棋盤格內(nèi)角點數(shù)量例如 9x6 pattern_size (9, 6) # 棋盤格方格實際邊長單位 mm square_size 5.0 # 生成棋盤格世界坐標 objp np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) objp * square_size obj_points [] # 3D 點 img_points [] # 2D 點 # 讀取多張不同角度的標定圖 for fname in [calib_01.jpg, calib_02.jpg, calib_03.jpg]: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, pattern_size, None) if ret: # 亞像素角點優(yōu)化提升標定精度 corners_refined cv2.cornerSubPix( gray, corners, (11, 11), (-1, -1), criteria(cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) ) obj_points.append(objp) img_points.append(corners_refined) # 相機標定 ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None ) print(內(nèi)參矩陣:\n, mtx) print(畸變系數(shù):\n, dist)這段代碼的關鍵參數(shù)是pattern_size和square_size必須和實際標定板一致填錯會導致標定結果整體縮放錯誤。cornerSubPix的窗口大小(11, 11)是經(jīng)驗值太小對噪聲敏感太大在角點密集時容易把鄰近角點算進來。標定圖建議拍 10 到 15 張覆蓋視野的四個角和中心標定重投影誤差控制在 0.1 像素以內(nèi)再進入下一步。2.2 邊緣提取與亞像素定位Canny 之后還要做什么金屬件在背光或同軸光下成像邊緣對比度高Canny 是最常用的邊緣檢測算子。但 Canny 輸出的是像素級邊緣直接拿來做尺寸測量重復性大概在 ±1 像素換算到 0.05mm/像素的分辨率就是 ±0.05mm達不到產(chǎn)線要求。所以必須做亞像素邊緣定位。常見做法有兩種一是用cv2.cornerSubPix的思路對邊緣點做梯度方向的亞像素插值二是用卡尺工具Caliper沿一條搜索線找灰度梯度極值點再做拋物線擬合。OpenCV 本身沒有現(xiàn)成的卡尺函數(shù)但可以用cv2.Sobel算梯度再沿搜索線找極值。def subpixel_edge(gray, p1, p2, search_width20): 沿 p1-p2 方向搜索亞像素邊緣點 # 在 p1-p2 連線上采樣 num int(np.linalg.norm(np.array(p2) - np.array(p1))) xs np.linspace(p1[0], p2[0], num) ys np.linspace(p1[1], p2[1], num) # 雙線性插值取灰度 vals [] for x, y in zip(xs, ys): vals.append(cv2.getRectSubPix(gray, (1, 1), (float(x), float(y)))[0, 0]) vals np.array(vals, dtypenp.float32) # 一階差分找梯度極值 grad np.abs(np.diff(vals)) idx np.argmax(grad) # 拋物線擬合求亞像素位置 if 0 idx len(grad) - 1: y0, y1, y2 grad[idx - 1], grad[idx], grad[idx 1] delta 0.5 * (y0 - y2) / (y0 - 2 * y1 y2) sub_idx idx delta else: sub_idx idx # 映射回坐標 t sub_idx / (num - 1) return p1[0] t * (p2[0] - p1[0]), p1[1] t * (p2[1] - p1[1])search_width控制搜索范圍太寬會把鄰近邊緣算進來太窄可能找不到真實邊緣。拋物線擬合的假設是邊緣附近梯度分布近似二次曲線對高斯型邊緣成立對階躍型邊緣會有偏差。如果金屬件邊緣有毛刺或反光建議先做高斯濾波再算梯度濾波核大小取 3 或 5太大邊緣會糊。2.3 尺寸換算與公差判定像素比例怎么用拿到亞像素邊緣點后兩個邊緣點之間的像素距離乘以「毫米/像素」比例就是物理尺寸。這個比例來自 2.1 節(jié)的平面標定。如果工件有厚度正投影測量的是上表面輪廓段差測量需要另做方案比如用激光三角測量或結構光純 2D 視覺測不了 Z 向尺寸。公差判定就是拿測量值和圖紙公差帶比對。這里有個細節(jié)測量值本身有不確定度如果公差帶很窄比如 ±0.01mm而你的測量重復性是 ±0.02mm那判定結果沒有意義。所以先做 GRR量具重復性與再現(xiàn)性分析確認測量系統(tǒng)能力足夠再上線判定邏輯。# 假設已標定出 mm_per_pixel mm_per_pixel 0.05 def measure_distance(edge1, edge2): 計算兩個亞像素邊緣點之間的物理距離 px_dist np.sqrt((edge1[0] - edge2[0])**2 (edge1[1] - edge2[1])**2) return px_dist * mm_per_pixel def judge(value, nominal, tol_plus, tol_minus): 公差判定 if nominal - tol_minus value nominal tol_plus: return OK return NGmm_per_pixel的精度直接決定測量精度標定時標準件的尺寸要用更高一級的量具測準比如用千分尺測 50mm 量塊的實際值而不是信標稱值。公差判定函數(shù)里tol_plus和tol_minus分開傳是因為很多金屬件公差不對稱比如 0.02/-0.01。2.4 多工件批量處理從單張圖到產(chǎn)線節(jié)拍產(chǎn)線上相機是連續(xù)采圖的算法要能批量處理并輸出結構化結果。常見做法是用一個主循環(huán)讀圖、測量、寫結果結果存 CSV 或直接通過串口/PLC 發(fā)給執(zhí)行機構。如果節(jié)拍要求高可以把測量邏輯封裝成函數(shù)用多進程或 C 加速Python 層只做調(diào)度。import glob import csv results [] for fname in sorted(glob.glob(prod_*.jpg)): img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 這里調(diào)用前面的邊緣提取和測量函數(shù) # edge_left subpixel_edge(gray, (100, 300), (100, 500)) # edge_right subpixel_edge(gray, (900, 300), (900, 500)) # width measure_distance(edge_left, edge_right) # status judge(width, nominal50.0, tol_plus0.02, tol_minus0.01) # results.append([fname, width, status]) pass with open(measure_result.csv, w, newline) as f: writer csv.writer(f) writer.writerow([file, width_mm, status]) writer.writerows(results)批量處理時要注意圖像命名排序sorted按字符串排如果文件名是prod_1.jpg到prod_10.jpg會出現(xiàn)prod_10排在prod_2前面建議文件名補零。結果 CSV 的字段設計要包含原始文件名、測量值、判定結果和測量時間戳方便追溯。3. 環(huán)境搭建與最小可跑通流程3.1 Python 與 OpenCV 安裝避開 ModuleNotFoundError新手最常遇到的報錯是ModuleNotFoundError: No module named opencv和cv2.error: OpenCV(4.4.0) ...。前者是沒裝 opencv-python后者多半是版本不匹配或安裝不完整。我一般用 conda 建虛擬環(huán)境再 pip 裝 opencv-python 和 opencv-contrib-pythoncontrib 包里有一些額外算法雖然尺寸測量用不到但后續(xù)擴展方便。conda create -n measure python3.9 -y conda activate measure pip install opencv-python4.8.0.74 opencv-contrib-python4.8.0.74 pip install numpy pandas裝完后驗證import cv2 print(cv2.__version__)如果import cv2報錯但 pip 顯示已安裝檢查是不是在錯誤的 Python 環(huán)境里執(zhí)行。VS Code 里要選對解釋器PyCharm 里要確認項目解釋器路徑。樹莓派上裝 OpenCV 建議用pip install opencv-python-headless省掉 GUI 依賴編譯時間也短。3.2 從一張圖到一組尺寸最小測量腳本下面是一個最小可跑通的測量腳本假設圖像里有一個矩形金屬件背景是背光板邊緣清晰。腳本做三件事讀圖、Canny 找邊緣、霍夫直線擬合出四條邊再算長寬。import cv2 import numpy as np img cv2.imread(metal_part.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯濾波降噪 blur cv2.GaussianBlur(gray, (5, 5), 0) # Canny 邊緣檢測閾值根據(jù)實際圖像調(diào)整 edges cv2.Canny(blur, 50, 150) # 霍夫直線檢測 lines cv2.HoughLinesP(edges, 1, np.pi / 180, threshold100, minLineLength200, maxLineGap10) # 按角度分組找水平和垂直方向的主邊 horizontal, vertical [], [] for line in lines: x1, y1, x2, y2 line[0] angle np.degrees(np.arctan2(y2 - y1, x2 - x1)) if abs(angle) 10: horizontal.append(line[0]) elif abs(abs(angle) - 90) 10: vertical.append(line[0]) # 取最外側的邊計算尺寸簡化處理 if horizontal and vertical: ys [p[1] for l in horizontal for p in [(l[0], l[1]), (l[2], l[3])]] xs [p[0] for l in vertical for p in [(l[0], l[1]), (l[2], l[3])]] height_px max(ys) - min(ys) width_px max(xs) - min(xs) mm_per_pixel 0.05 # 需根據(jù)標定替換 print(f寬度: {width_px * mm_per_pixel:.3f} mm) print(f高度: {height_px * mm_per_pixel:.3f} mm)HoughLinesP的threshold是累加器閾值值越大檢出的線越少但越可靠minLineLength過濾短線段金屬件邊緣長可以設大一點maxLineGap允許線段中間斷開適合有倒角或圓角的邊。這個腳本只是基線實際產(chǎn)線要用亞像素邊緣和卡尺工具替換霍夫直線精度才能上去。3.3 標定文件與測量腳本的對接標定得到的mtx和dist要存成文件測量腳本啟動時加載避免每次重新標定。常見做法是存成.npz或.yaml。# 保存標定結果 np.savez(calib.npz, mtxmtx, distdist, mm_per_pixel0.05) # 測量腳本加載 calib np.load(calib.npz) mtx calib[mtx] dist calib[dist] mm_per_pixel float(calib[mm_per_pixel])mm_per_pixel建議單獨存一個標量不要從內(nèi)參矩陣反推因為平面比例還和相機高度、鏡頭焦距有關直接標定更可靠。如果產(chǎn)線換鏡頭或調(diào)了相機高度必須重新標定并更新這個文件。4. 避坑與排查金屬件測量常見的五個翻車點4.1 邊緣反光導致測量值跳變現(xiàn)象同一工件連續(xù)拍十張寬度測量值在 0.05mm 范圍內(nèi)跳動偶爾跳 0.1mm 以上。原因金屬件表面反光邊緣附近灰度梯度不穩(wěn)定Canny 閾值固定時反光區(qū)域會產(chǎn)生偽邊緣或漏檢。解決改用背光或同軸光讓工件呈剪影如果必須用表面光加偏振片壓反光同時把 Canny 閾值改成自適應用cv2.threshold的 Otsu 方法先二值化再找輪廓。4.2 鏡頭畸變沒校正邊緣位置隨工件移動變化現(xiàn)象工件放在視野中心時測量準移到邊緣就偏 0.1mm 以上。原因鏡頭畸變沒校正或者只做了內(nèi)參標定沒做畸變校正。解決用cv2.undistort對每張圖先做畸變校正再測量。校正后的圖會損失一部分視野邊緣相機選型時要把這個余量算進去。4.3 標定板不平或標定圖太少現(xiàn)象標定重投影誤差看著很小但實際測量就是不準。原因標定板沒放平或者標定圖角度太單一解出的參數(shù)過擬合。解決標定板用陶瓷或玻璃基板別用打印紙標定圖至少 10 張覆蓋視野四角和中心傾斜角度在 ±30 度內(nèi)變化。4.4 像素比例用錯尺寸整體縮放現(xiàn)象所有測量值都偏大或偏小同一個比例。原因mm_per_pixel算錯比如標定件實際 50mm 但代碼里寫了 5mm或者單位混了。解決標定件尺寸用千分尺實測代碼里單位統(tǒng)一用毫米標定后拿一個已知尺寸的工件驗證偏差超過 0.01mm 就查比例。4.5 多線程讀圖導致結果錯亂現(xiàn)象批量處理時結果和文件名對不上或者程序偶爾崩潰。原因多線程讀圖時共享了全局變量或者 OpenCV 的imread在多線程下不是線程安全的。解決讀圖用單線程測量邏輯可以并行但每個線程用獨立的圖像副本別共享numpy數(shù)組。5. 精度再往上走亞像素卡尺工具與 GRR 驗證如果產(chǎn)線要求 ±0.01mm 的重復性前面那套 Canny 加霍夫直線的方案就不夠了。我一般會自己寫一個卡尺工具在邊緣附近沿搜索線取灰度剖面做高斯擬合求極值點再把多個搜索線的結果取平均。這樣單邊邊緣的定位重復性能到 0.1 像素以內(nèi)換算到 0.02mm/像素的分辨率就是 ±0.002mm。def caliper_edge(gray, start, end, num_lines10, search_len30): 卡尺工具多條搜索線取平均 points [] for i in range(num_lines): t i / (num_lines - 1) cx start[0] t * (end[0] - start[0]) cy start[1] t * (end[1] - start[1]) # 沿垂直方向搜索 p1 (cx - search_len / 2, cy) p2 (cx search_len / 2, cy) edge subpixel_edge(gray, p1, p2) points.append(edge) # 取平均 xs [p[0] for p in points] ys [p[1] for p in points] return np.mean(xs), np.mean(ys)num_lines越多平均效果越好但計算量也越大。產(chǎn)線節(jié)拍允許的話取 20 到 30 條。search_len要覆蓋邊緣可能的漂移范圍一般取 20 到 50 像素。驗證測量系統(tǒng)能力用 GRR選 10 個工件每個工件測 3 次換 2 個操作員各測一輪算重復性和再現(xiàn)性。%GRR 低于 10% 說明測量系統(tǒng)合格10% 到 30% 可接受但要看應用場景超過 30% 就得改方案。這個分析用 Python 的pandas和numpy就能算不需要專門軟件。我自己踩過的坑是標定完覺得萬事大吉直接上線結果夜班燈光變化導致邊緣閾值失效一批件全判 NG。后來養(yǎng)成習慣每次換班先拿標準件跑一遍確認測量值在公差中值附近再開線。希望幫到你。本文還有配套的精品資源點擊獲取