
文章目录用 A4 纸打印一张「尺寸准确、远处也能识别」的 ChArUco 标定板一、先搞清楚ChArUco 是什么两个尺寸参数指什么二、最简单的生成OpenCV三、三个打印大坑坑 1打印机自动缩放坑 2PDF 的物理页面尺寸在生成时就被写错坑 3分辨率太高页面比纸大选 100% 只印出中心一小块四、稳妥解法让物理尺寸和像素分辨率彻底解耦五、关键一步按相机内参反推「多远能识别到」六、按需定尺寸把板子放大到铺满 A4七、两个必须记住的一致性要点八、小结用 A4 纸打印一张「尺寸准确、远处也能识别」的 ChArUco 标定板做相机标定时很多人第一步就翻车随手生成一张 ChArUco 板、丢给打印机、拿尺子一量——方格既不是设定的尺寸摆远一点相机还识别不到。本文把从「生成」到「打印物理尺寸准确」再到「按相机内参反推识别距离」的完整方法整理出来附可直接运行的脚本。整体路线如下距离处太小则回头放大板子① 生成 ChArUco 图像OpenCV cv2.aruco② 打印物理尺寸准确reportlab 锁毫米 实际大小 100%③ 按相机内参反推识别距离像素 f × M / D一、先搞清楚ChArUco 是什么两个尺寸参数指什么ChArUco 板是棋盘格Chessboard ArUco 标记的组合棋盘格的黑白角点提供亚像素级的精确定位嵌在白格里的 ArUco 标记则用来识别板子朝向、定位当前可见的是哪些棋盘格角点从而给每个角点一个全局唯一编号。即使标定板被部分遮挡靠编号也能识别出剩余角点比纯棋盘格鲁棒得多。生成时有两个关键尺寸Square Length方格边长棋盘格一个方格的边长。Marker Length标记边长嵌在白格里的 ArUco 标记的边长必须小于Square Length四周留出白边检测才稳。两者常见比例约为Marker ≈ 0.7 ~ 0.75 × Square。二、最简单的生成OpenCVOpenCV 的cv2.aruco模块直接能生成。注意CharucoBoard的尺寸参数在生成图像阶段是像素物理尺寸由打印环节决定importcv2fromcv2importaruco# (cols, rows) (5, 7)即 5 列 7 行aruco_dictaruco.getPredefinedDictionary(aruco.DICT_6X6_250)boardaruco.CharucoBoard((5,7),squareLength200,markerLength150,dictionaryaruco_dict)imgboard.generateImage((1000,1400))# 输出像素尺寸cv2.imwrite(charuco.png,img)到这里你会拿到一张图。但真正的坑在打印。三、三个打印大坑坑 1打印机自动缩放直接把图片丢进打印预览默认往往是「适应纸张」——它会把图按纸张大小缩放你设定的 25mm 方格印出来可能变成 40mm。打印时必须选「实际大小 / 100%」关掉任何「适应纸张 / 自动缩放」。坑 2PDF 的物理页面尺寸在生成时就被写错先厘清一个常见误解PDF 的页面物理尺寸是绝对写死在文件里的MediaBox单位是 point 1/72 英寸阅读器不会、也无需用 DPI 去猜页面多大。所以如果打印对话框里显示的尺寸不对错误不是阅读器解读出来的而是生成阶段就烤进了文件。实测就踩了这个坑我用 PIL 把图存成 PDF 并写了dpi(96,96)本以为得到一张 A421×29.7cm结果打印对话框显示成了 28×39.6cm。原因是Pillow 的 PDF 导出在不少版本里并不真正采用这个dpi参数、而是回落到 72 DPI于是 793px 的图被当作793 ÷ 72 × 25.4 279.7mm ≈ 28cm写进了页面宽度高度 1122px →1122 ÷ 72 × 25.4 ≈ 39.6cm两个维度都指向 72 DPI 生成。阅读器只是忠实显示了这个被写错的尺寸。根因把「物理尺寸」隐式地绑定在「图像像素 DPI 元数据」上而这条 DPI 通路尤其 PIL 存 PDF不可靠——它悄悄回落到 72 时物理尺寸就整个错位。坑 3分辨率太高页面比纸大选 100% 只印出中心一小块如果直接用 300 DPI 生成整页图2480×3508px有些阅读器会把它当成一张超大纸选「实际大小」时只印出页面中心的一小块。四、稳妥解法让物理尺寸和像素分辨率彻底解耦三个坑各自对应的解法可以先看这张对应关系图——坑 1 靠打印设置坑 2/坑 3 靠 reportlab两者合起来才拿到正确的物理尺寸坑1 打印机自动缩放打印选「实际大小 / 100%」坑2 PDF 物理尺寸生成时写错PIL 回落 72 DPIreportlab 按物理毫米摆到 A4物理尺寸与像素解耦坑3 分辨率过高100% 只印中心一小块尺子量方格 40mm ✓思路不要靠 DPI 元数据传递物理尺寸。改用reportlab建一张真正的 A4 画布把棋盘图像按物理毫米精确摆放上去。这样图像分辨率只决定清晰度物理尺寸由「毫米坐标」直接锁死打印选「实际大小」就一定准。importcv2fromcv2importarucofromreportlab.lib.pagesizesimportA4fromreportlab.lib.unitsimportmmfromreportlab.pdfgenimportcanvasfromreportlab.lib.utilsimportImageReaderfromPILimportImage# 目标物理尺寸 COLS,ROWS5,7SQUARE_MM40.0MARKER_MM30.0# 生成高分辨率棋盘图像分辨率只影响清晰度render_dpi300px_per_mmrender_dpi/25.4square_pxround(SQUARE_MM*px_per_mm)marker_pxround(MARKER_MM*px_per_mm)aruco_dictaruco.getPredefinedDictionary(aruco.DICT_6X6_250)boardaruco.CharucoBoard((COLS,ROWS),squareLengthsquare_px,markerLengthmarker_px,dictionaryaruco_dict)imgboard.generateImage((COLS*square_px,ROWS*square_px),marginSize0)img_pilImage.fromarray(img)# reportlab 在 A4 上按物理毫米精确放置、居中 board_w_mmCOLS*SQUARE_MM# 200 mmboard_h_mmROWS*SQUARE_MM# 280 mmpage_w,page_hA4# 210 x 297 mm单位 pointx(page_w-board_w_mm*mm)/2y(page_h-board_h_mm*mm)/2ccanvas.Canvas(charuco_A4.pdf,pagesizeA4)c.drawImage(ImageReader(img_pil),x,y,widthboard_w_mm*mm,heightboard_h_mm*mm)c.showPage()c.save()打印这张charuco_A4.pdf选「实际大小 / 100%」拿尺子量方格——就是 40mm。之所以能根治正是因为 reportlab 绕开了 PIL 那条不可靠的 DPI 通路直接把正确的物理尺寸写进 A4 页面的 MediaBox。这里generateImage(..., marginSize0)让棋盘外不留白边是安全的因为 reportlab 把它居中放在整张白色 A4 上、四周自然留出了空白ArUco 检测所需的静默区 quiet zone 就来自这片空白。但如果你改成贴边打印、或把图裁到刚好只剩棋盘就会丢掉这圈静默区导致检测变差——那种场景要显式保留 margin。五、关键一步按相机内参反推「多远能识别到」尺寸准了还不够。我遇到的真实问题是25mm/18mm 的小板子摆到 50cm 处相机就识别不到了。要不要放大、放多大不能凭感觉要用相机内参算。针孔模型下一个物理尺寸为M米的物体在距离D米处成像的像素大小为像素 f × M / D其中f是相机焦距像素。这是物体正对相机、且fx ≈ fy前提下的量级估算用于判断够不够大足矣不必当作图像边缘、大畸变区的精确值。以我的相机为例一组双目标定得到 f 1459 px单目分辨率 1520×1520物理尺寸50cm 处成像Square 25mm1459 × 0.025 / 0.5 ≈ 73 pxMarker 18mm1459 × 0.018 / 0.5 ≈ 52 px一个 DICT_6X6 的 ArUco加上四周边框共8 个模块。52px 的 marker 意味着每个模块只有52 / 8 ≈ 6.5 px——稍有离焦、运动模糊或斜视角检测就崩了。这正是「50cm 看不见」的原因。经验上ArUco marker 的成像至少要几十像素、每模块 ≥ 3~4px 才比较稳越大越好。六、按需定尺寸把板子放大到铺满 A4要让 marker 在 50cm 处翻倍到 ~88px反推物理尺寸并考虑 A4 上限。A4 竖版放 5×7单格上限是min(210/5, 297/7) ≈ 42mm。取Square 40mm / Marker 30mm留边距重新算各距离距离Square (40mm)Marker (30mm)0.3m195 px146 px0.5m117 px88 px0.8m73 px55 px1.0m58 px44 px50cm 处 marker 从 52px 提到 88px检测就稳了。这也是单张 A4 的物理极限。如果还需要更大比如要在 1m 处稳单张 A4 已到头只能减少格数如 4×6单格可到 ~48mm换 A3 纸单格可到 ~57mm换更粗的字典DICT_5X5 或 DICT_4X4同样的 marker 物理尺寸下模块更少、每模块更大更抗距离和模糊。代价是可编码的 marker 数量更少、码间汉明距离更小、抗误检能力略降——对 5×7 这种小板无妨但大板或多板场景要留意另外检测代码里的字典也要同步改。七、两个必须记住的一致性要点打印务必选「实际大小 / 100%」否则前面所有物理尺寸的努力全白费。打印后用尺子实测方格边长核对。改了标定板的物理尺寸标定代码里的squareLength/markerLength必须同步改成对应的米数如 0.040 / 0.030。这两个值决定了标定出来的尺度——它们错了相机外参的平移量、以及基于视差换算的深度都会整体缩放错。注意区分标定板的物理尺寸不影响相机内参内参是像素单位、与标定板实际多大无关写错也照样能标出正确的 fx/fy/畸变它只影响外参平移量和度量深度。正因为内参不受影响这个错误特别隐蔽——重投影误差看起来正常只有最终的距离/尺度整体偏了。八、小结ChArUco 棋盘格 ArUcoMarker 要小于 Square 并留白边。打印翻车三连自动缩放、PDF 生成时物理页面尺寸被写错PIL 回落 72 DPI、分辨率过高只印中心。稳妥解法用 reportlab 把图按物理毫米摆到 A4 画布上物理尺寸与像素解耦打印选「实际大小」。尺寸够不够用像素 f × M / D按相机内参算别凭感觉。物理尺寸、打印缩放、标定代码里的参数三处必须一致。