
手眼標定眼在手中眼在手外——這四個字看起來簡單但凡是做過機器人視覺引導項目的人都知道這是整個系統(tǒng)里最容易讓人失眠的環(huán)節(jié)。你搭好了相機、裝好了機械臂、寫好了識別算法結果機械臂怎么都抓不準誤差從幾毫米到幾厘米飄忽不定最后排查下來十有八九是手眼標定這一步出了問題。這篇文章我就把這塊的內容完整拆開從原理到實操從眼在手中到眼在手外把坐標變換的來龍去脈、標定步驟、常見坑點一次講透適合剛接觸機器人視覺的工程師也適合被標定精度折磨過一陣子的老手對照排查。1. 手眼標定到底在解決什么問題1.1 機器人的眼睛和手各說各話先說一個最直觀的感受機械臂能拿到一個物體靠的是末端執(zhí)行器精確移動到某個坐標位置。而相機能看到物體輸出的是像素坐標或者相機坐標系下的三維坐標。問題是相機坐標系和機器人坐標系之間沒有任何天然的對應關系——你在圖像里看到一個螺絲釘在畫面正中央但機械臂到底要往哪個方向伸、伸多遠完全取決于相機和機器人之間的空間關系。這個關系不是靠眼睛看出來的也不是靠尺子量出來的。相機裝在機械臂旁邊或者末端上角度、距離、高度稍有偏差機械臂的目標位置就會跟著偏。手眼標定要做的事情就是通過一系列數學計算把相機看到的物體坐標換算成機械臂能執(zhí)行的機器人坐標。這個換算關系一旦出錯后面所有的視覺識別精度都白搭。有人可能會說那我用尺子量一下相機相對機械臂的位置角度手動換算不行嗎理論上是可行的但實際幾乎沒人這么干。因為相機的鏡頭光軸、成像中心、安裝傾角都不是肉眼能精確測量的幾度的角度誤差在遠距離上就是幾十毫米的位置偏差。標定算法通過多組測量數據反解出變換關系比手工測量可靠得多這也是為什么手眼標定成了視覺引導項目里不可跳過的一步。1.2 眼在手中和眼在手外的坐標系關系眼在手中eye-in-hand指的是相機固定在機械臂末端法蘭上跟著機械臂一起運動。這種情況下相機坐標系和機械臂末端坐標系是相對固定的但機械臂在運動所以相機在世界中的位置也在不斷變化。標定要求解的是相機坐標系到機械臂末端坐標系的齊次變換矩陣記為X T_cam_to_tool。眼在手外eye-to-hand指的是相機固定在機械臂之外的某個位置比如支架上、天花板上、工作臺上方相機不動機械臂在它的視野里運動。這種情況下相機坐標系和機器人基座坐標系才是相對固定的標定要求解的是相機坐標系到機器人基坐標系的變換矩陣記為X T_cam_to_base。這兩種安裝方式的求解目標不同數據采集方式也不同但底層數學都是同一個問題AX XB。這個方程看著抽象實際上描述的是一個閉合的坐標變換鏈——機械臂帶著末端動一圈相機每次都記錄下標定板在相機里的位姿同時記錄下機械臂末端的位姿聯(lián)立方程把未知的固定變換解出來。理解了這條坐標鏈后面的所有操作步驟就都順理成章了。1.3 AXXB的通俗理解AX XB 這個方程說白了就是我從兩個不同路徑走到同一個房間路徑不一樣但終點一樣于是兩個路徑的變換關系可以聯(lián)立。具體到手眼標定假設標定板固定不動機械臂帶著或不帶相機運動到多個位置A 是機械臂末端在兩個位姿之間的運動變換由機器人示教器或控制接口直接讀出。B 是相機在兩次拍照之間觀察到的標定板位姿變化由標定板識別得到。X 是我們想求的固定變換眼在手中時是相機到末端的變換眼在手外時是相機到基座的變換。多取幾組位姿就有了多個 AX XB 方程數值求解得到最優(yōu)的 X。這些年 OpenCV 的calibrateHandEye函數已經把常用的經典算法Tsai-Lenz、Park-Martin 等封裝好了輸入肉眼看不懂輸出就是一個 4×4 齊次變換矩陣。理解到這一層就夠了真正的工作量在數據采集和質量把控上而不是在數學推導上。2. 兩種安裝方式怎么選別等裝好了再后悔2.1 眼在手中的優(yōu)勢與適用場景眼在手中最直觀的好處是相機離作業(yè)目標近。視野小、分辨率高螺絲孔、小零件、表面缺陷這些細節(jié)能看得很清楚精度潛力比固定相機大得多。其次機械臂動到哪相機就跟到哪總能找到合適的角度去看目標不用擔心被其他物體擋住視線。比如在狹窄的機箱內部插線、小零件抓取、焊縫跟蹤這些需要貼近看、照著干的場景眼在手中基本是默認選擇。但它也有明顯的代價。相機跟著機械臂動運動慣性會影響整機的動態(tài)精度而且每次拍照時機械臂必須穩(wěn)定到位不然畫面模糊直接導致識別失敗。另外一個容易被忽視的問題是眼在手中標定時標定板必須固定在工作空間里機械臂帶著相機在不同角度拍照這就對機械臂的工作空間有要求活動范圍太小會嚴重影響標定質量。我見過不少項目機械臂工作空間本身就緊湊還要騰出標定位置最后只能把標定板往邊緣放標定出來的結果自然不理想。2.2 眼在手外的優(yōu)勢與適用場景眼在手外是另一種思路相機固定在龍門架、支架或工作臺上俯視整個作業(yè)區(qū)域。最大的優(yōu)勢是視野覆蓋大且穩(wěn)定適合大范圍定位、物料來料檢測、多工位共享一個相機。機械臂運動時相機不受任何振動影響圖像質量穩(wěn)定標定一次之后只要相機不碰長時間不用重新標。對很多流水線場景來說眼在手外更省心。缺點也很現(xiàn)實。相機離得遠視野大但分辨率被攤薄識別小物體時精度有限。俯視角度也意味著低矮物體、堆疊物體的側面信息看不到有些姿態(tài)識別做不了。還有一個比較大坑相機和機械臂如果距離太遠標定板放在機械臂末端時相機的安裝角度和距離會導致標定板成像過小或過大直接影響標定板角點檢測的穩(wěn)定性。這也是為什么眼在手外標定時很多工程師傾向于選較大的標定板而不能隨手拿個小的湊合用。2.3 選型決策要點拿不準選哪種的時候我從項目經驗里總結了幾個判斷點判斷維度眼在手中眼在手外目標物大小小零件、細節(jié)特征中大物體、整面覆蓋視野需求局部高精度全局覆蓋遮擋情況機器人靠近觀察、規(guī)避遮擋容易被機械臂自身遮擋標定頻率換末端夾具或相機松動時需要重標相機固定穩(wěn)定可長期復用安裝空間相機占用末端載荷和空間需要外部支架或龍門運動影響圖像受運動振動影響圖像完全不受運動影響如果項目里既有全局定位需求又要有細節(jié)檢測還有些人會做成雙相機方案一個眼在手外負責找大范圍目標粗定位另一個眼在手中跟著末端走精定位。這種組合在不少復雜工位上是比較實用的做法但前期調試量也翻倍初學者不建議一上來就雙相機。我的建議是先根據主要作業(yè)目標確定主方案復雜度控制在一個相機穩(wěn)定跑通之后再考慮加裝。3. 手眼標定的完整實操流程3.1 標定板的選型與準備標定板是手眼標定的基準參照物質量直接決定標定成敗。市面上常見的三類棋盤格、圓點陣、二維碼/ArUco 板。棋盤格最容易獲得紙質打印就能用OpenCV 的findChessboardCorners成熟穩(wěn)定適合快速驗證流程但紙質板容易彎折、反光精度天花板有限。圓點陣對畸變和模糊更魯棒圓心提取精度比角點高適合高精度標定。ArUco / AprilTag 的優(yōu)勢在于單張圖就能提供唯一的位姿信息部分算法用它做實時標定更靈活但對相機離焦比較敏感。尺寸選擇有一個很實用的經驗法則標定板邊長至少占相機視野的 1/3 到 1/2。太小了角點檢測不穩(wěn)定太大了在近距離下又拍不全。眼在手中因為相機跟得近標定板比眼在手外可以小一些眼在手外相機離得遠標定板盡量選大方形的。我自己的習慣是打印后貼在剛性背板上比如鋁板、亞克力板用雙面膠貼平后壓重物靜置一晚確保板面平整。這一步看著不起眼但板面不平造成的誤差后面靠算法怎么調都調不回來。3.2 數據采集的關鍵姿勢與數量控制標定精度很大程度上由數據決定而不是由算法決定。數據采集的核心要求是姿態(tài)要多樣位置要分散覆蓋整個工作區(qū)域。具體來說至少采集 15 到 20 組有效圖像每組對應機械臂的一個位姿。每組之間要保證機械臂末端的姿態(tài)要有明顯變化光平移不轉或者光轉不平移都容易讓 AXXB 方程退化解出來的 X 精度很差。標定板成像在視野內盡量往四角和邊緣靠不要總在正中心徘徊。相機鏡頭的畸變在邊緣區(qū)域表現(xiàn)得更明顯標定數據覆蓋邊緣有助于算法把畸變和手眼關系分開。機械臂的運動范圍要覆蓋后續(xù)作業(yè)會用到的區(qū)域不能只在某個角落轉小圈。還有一個很多人會忽略的點每組數據記錄時機械臂必須到達目標位姿并完全穩(wěn)定之后再拍照。如果機械臂還在抖動或者移動中就觸發(fā)拍照標定板位姿識別會產生隨機誤差每一組都帶噪聲最終標定結果就是抖的??刂葡鄼C觸發(fā)的信號我一般建議直接接機械臂的輸出端口到位后延時 300 到 500 毫秒再拍比在工控機里靠線程延時靠譜得多。3.3 標定求解OpenCV工具鏈與核心代碼數據采集完之后求解階段用 OpenCV 的calibrateHandEye就夠了。下面這段是標準流程基于 Python 和 OpenCV我實際項目里一直在用直接套用即可。import cv2 import numpy as np import glob # 讀入所有標定板圖像 images sorted(glob.glob(calib_images/*.jpg)) pattern_size (9, 6) # 棋盤格內角點數 square_size 30.0 # 格邊長單位mm # 標定板在相機坐標系下的位姿以及末端位姿的容器 rvecs_cam [] tvecs_cam [] rvecs_robot [] tvecs_robot [] # 1. 從圖像中提取標定板角點并解算相機外參 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 # 相機內參從單獨的相機標定中獲得或先用近似值 camera_matrix ... # 3x3內參矩陣 dist_coeffs ... # 畸變系數 for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, pattern_size, None) if not ret: continue # 亞像素角點精化 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 50, 1e-6) corners cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) # 解算標定板相對相機的位姿 ret, rvec, tvec cv2.solvePnP(objp, corners, camera_matrix, dist_coeffs) rvecs_cam.append(rvec) tvecs_cam.append(tvec) # 從機械臂控制接口讀取當前末端位姿轉成旋轉向量和平移向量 robot_pose get_robot_pose() # 自行實現(xiàn)返回4x4齊次矩陣或歐拉角 rvec_robot, tvec_robot pose_to_rvec_tvec(robot_pose) rvecs_robot.append(rvec_robot) tvecs_robot.append(tvec_robot) # 2. 手眼標定求解 R_cam2tool, t_cam2tool cv2.calibrateHandEye( rvecs_robot, tvecs_robot, # 機械臂末端運動 rvecs_cam, tvecs_cam, # 相機觀測標定板運動 methodcv2.CALIB_HAND_EYE_TSAI ) # 3. 組裝齊次變換矩陣 H_cam2tool np.eye(4) H_cam2tool[:3, :3] R_cam2tool H_cam2tool[:3, 3] t_cam2tool.flatten() print(H_cam2tool)眼在手外的求解過程幾乎一樣區(qū)別只在讀取機械臂位姿的時候用的是基坐標系下的機械臂末端位姿而且標定板一般固定在機械臂末端法蘭上數據記錄順序和輸入參數要對應好。很多人在這一步栽跟頭就是把兩種安裝方式的輸入搞反了。3.4 標定結果怎么驗證才算通過標定完不是看一眼矩陣就收工必須做驗證。最常用的方法有兩種重投影驗證和實際點驗證。重投影驗證的思路是用標定得到的手眼矩陣把標定板上的已知點反投到圖像坐標中和實際檢測到的像素坐標對比計算重投影誤差。這個誤差在亞像素到 1 到 2 個像素內說明標定結果在圖像層面是自洽的。但注意一點重投影誤差只反映數據擬合的殘差不代表機器人抓取的實際精度因為它沒法暴露機械臂自身絕對定位誤差帶來的影響。所以更關鍵的是實際點驗證把標定板或者一個特征明顯的小工件放在相機視野內的多個位置用視覺檢測出它的坐標再通過手眼矩陣換算成機器人坐標讓機械臂末端移動到該坐標看實際對準誤差。一般來說平移誤差控制在 1 到 3 毫米內是及格線配合高精度末端工具可以做到 0.5 毫米內旋轉誤差最好在 0.1 度到 0.2 度以內。如果驗證結果超出這個范圍先不要急著調算法回頭檢查數據采集的質量多數問題出在那里。4. 實操中踩過的坑與排查技巧4.1 標定板檢測失敗的常見原因標定板檢測失敗是最容易讓人沮喪的問題因為報錯往往不明確。我遇到的情況大致有幾類第一類是光照問題。標定板表面反光尤其是棋盤格的白格在強光下過曝角點附近的高光會把角點吞掉。解決辦法是調整光源角度不要讓光源對著標定板直射漫射光或者角度低一點的側光效果會好很多。這類問題用眼睛看圖像無法發(fā)現(xiàn)因為人眼能腦補出邊界但算法不行一定要把圖像放大逐塊看。第二類是標定板尺寸和視野的比例不合適。棋盤格在畫面里占的像素太少角點之間的間距只有幾個像素亞像素精化也沒有意義。遇到這種情況要么換更大號的標定板要么讓相機離標定板更近保證角點間距至少在 10 個像素以上。第三類是運動模糊。機械臂到位后沒有穩(wěn)定就觸發(fā)拍照圖像邊緣有輕微的拖影角點檢測就會識別出斜向誤差。排查方法是拍照后立刻查看圖像是否清晰標定板邊緣是否銳利不行就在相機觸發(fā)邏輯上加延時。4.2 標定結果發(fā)散或誤差大的排查思路標定出來的矩陣如果明顯不合理——比如平移分量有幾十毫米的突變、旋轉矩陣不正交、兩次標定結果差得離譜——先別懷疑算法公式寫錯了大概率是數據本身的病態(tài)問題。最典型的是姿態(tài)多樣性不夠。如果你采集的 20 組數據里機械臂末端姿態(tài)幾乎沒變只是左右前后平移那 AXXB 方程就處于退化狀態(tài)解出來的旋轉分量嚴重不可靠。這時候就算重采樣也要刻意讓機械臂做俯仰、偏航、滾轉的大角度運動姿態(tài)角度差異最好在 30 度以上才能把旋轉自由度約束住。另外一個常見的坑是坐標系混淆。機械臂接口返回的位姿有的是末端坐標系相對基坐標系的有的是工具坐標系相對基坐標系的如果你的工具坐標系有偏移比如裝了吸盤、氣爪直接拿來標定就會引入一個固定偏差。排查方法很簡單手動把機械臂末端轉個 90 度看讀取的坐標變化是否符合預期也可以在示教器上單獨查看工具坐標系的數值是否歸零。還有一個容易忽略的是機械臂的絕對定位精度。某些型號的機械臂重復定位精度很高比如 ±0.02 毫米但絕對定位精度可能只有 ±2 到 ±3 毫米這種差異會被標定算法當成噪聲吸收掉導致視覺引導的最終精度上限受制于機械臂自身。這種情況不是標定能彌補的要做的是在驗證環(huán)節(jié)就把機械臂的絕對定位能力測一遍心里有數。4.3 手眼矩陣驗證的獨門方法標定矩陣的驗證環(huán)節(jié)我有一個自己一直在用的“三點法”實施起來很快但很能說明問題在相機視野內的左、中、右三個區(qū)域各放一個特征點用視覺檢測出坐標換算成機器人坐標后控制機械臂去指。如果三個點的誤差方向一致且大小相近可能是手眼矩陣里某個平移分量有固定偏差如果誤差在不同區(qū)域差別很大通常是旋轉分量不對因為旋轉誤差在視野邊緣會被放大如果誤差隨機跳動優(yōu)先懷疑機械臂定位穩(wěn)定性或視覺識別本身的波動。這個方法的優(yōu)點是把誤差來源做了粗分離省得每次出問題都全鏈路排查。我做了這么多項目之后有個體會手眼標定是一次性的活兒但驗證是每次換工裝、換夾具、調相機之后都要做的安全網。很多現(xiàn)場故障看起來是識別算法的問題追根溯源往往是手眼矩陣已經失效了比如相機被碰歪了幾毫米提前做一個三點法驗證能省幾個小時的無頭緒排查。4.4 提高標定精度的幾個獨家技巧第一采集數據時給機械臂“多轉少移”。每次到位后讓末端姿態(tài)多做一些變化平移動量大但姿態(tài)變化小的數據對求解的貢獻遠不如姿態(tài)變化大的數據。我一般會刻意規(guī)劃幾組“大角度低頭看標定板”的姿態(tài)比如 30 度、45 度、60 度俯仰角各幾組效果好很多。第二標定板不要放在工作空間正中心附近。很多人習慣把標定板放在機器人正前方然后讓機械臂帶著相機原地轉圈拍。這樣采集的數據覆蓋范圍太集中標定出來的手眼矩陣在中心附近擬合得很好但到了工作區(qū)域邊緣就明顯偏。正確做法是讓標定板覆蓋整個工作平面的多個位置移動標定板而不是只移動機械臂。第三多標定幾次取平均。同一個數據集因為初值不同算法可能收斂到略有差異的解這不是算法不穩(wěn)定而是數值求解的正?,F(xiàn)象。我通常會連續(xù)標定 5 次把得到的五個矩陣兩兩比較看平移分量波動多少。波動在 1 毫米以內算穩(wěn)定如果波動過大說明數據質量還不夠加了再多數據也白搭。5. 最后分享一點個人體會做了這些年視覺引導項目我個人的體會是手眼標定這個東西技術含量并不在數學和代碼上而在數據采集和現(xiàn)場判斷上。OpenCV 給了一行函數真正決定成敗的是你有沒有耐心把 20 組高質量數據采出來有沒有把標定板貼平、把光源調好、把機械臂的姿態(tài)覆蓋到位。很多時候項目卡住不是卡在算法上而是卡在“差不多就行”的心態(tài)上。另外一個小建議標定完的矩陣一定要做好歸檔和版本管理。換相機、換鏡頭、換末端工具、甚至只是相機支架被人碰了一下手眼矩陣可能就失效了。我在現(xiàn)場的習慣是每次標定完把矩陣、采集的圖像編號、機械臂型號、工具坐標系的配置一起存成一個版本文件夾現(xiàn)場出問題時可以快速回溯是哪一次標定出了問題。這個習慣看起來不起眼但真正遇到半夜現(xiàn)場調試、設備被人動過又沒人承認的場合它比什么都管用。