投影仪光心 Op、相机光心 Oc基线长 B,被测点 P(X,Y,Z)投影为 pc(uc,vc)、 pp(up,vp),三维坐标计算公式:
Z=uc−fpfcupfcB,X=fcucZ,Y=fcvcZ
标定目标:相机内参 (fc,uc0,vc0,k1,k2,p1,p2)、投影仪内参 (fp,up0,vp0)、外参 (R,T)。
工业界标准混合编码方案,实现亚像素级相位测量:
四步相移包裹相位: ϕ(x,y)=arctan(I0−I2I3−I1),In=I0+I′cos(ϕ+2nπ)
格雷码相位展开: Φ(x,y)=ϕ(x,y)+2πk(x,y)相位展开精度: 1/100像素。
目标函数:
minR,T∑i=1N∥Rpi+T−qi∥2
通过 SVD 求解最优变换,迭代收敛阈值: 10−6m。
|
组件 |
工业级 |
高精度级 |
选型依据 |
|---|---|---|---|
|
工业相机 |
1280×1024,30fps,全局快门,3.75μm 像元 |
2560×1920,60fps,全局快门,2.2μm 像元 |
分辨率决定横向精度,像元尺寸决定信噪比 |
|
DLP 投影仪 |
1280×720,1000:1 对比度,LED 光源 |
1920×1080,2000:1 对比度,激光光源 |
对比度决定相位精度,光源亮度决定扫描速度 |
|
镜头 |
8-25mm,畸变 < 1% |
12-50mm,畸变 < 0.1% |
低畸变减少标定误差 |
|
基线 |
100-300mm |
200-500mm |
基线越长深度精度越高,盲区越大 |
|
指标 |
工业级 |
高精度级 |
|---|---|---|
|
测量范围 |
0.5-3m |
0.3-5m |
|
单点精度 |
±0.05mm |
±0.01mm |
|
点云密度 |
100 万点 / 帧 |
400 万点 / 帧 |
|
单帧时间 |
0.5s |
0.2s |
|
体积精度 |
±0.1mm/m |
±0.05mm/m |
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main() { Size boardSize(9,6);
float squareSize=25.0f;
vector<vector<Point2f>> imagePoints;
vector<vector<Point3f>> objectPoints;
for(int i=0;i<20;i++) { Mat img=imread(format("calib_%d.jpg",i));
vector<Point2f> corners; if(findChessboardCorners(img,boardSize,corners)) { cornerSubPix(img,corners,Size(11,11),Size(-1,-1), TermCriteria(TermCriteria::EPS+TermCriteria::MAX_ITER,30,0.001));
imagePoints.push_back(corners);
vector<Point3f> obj; for(int j=0;j<boardSize.height;j++) for(int k=0;k<boardSize.width;k++)
obj.push_back(Point3f(k*squareSize,j*squareSize,0));
objectPoints.push_back(obj);
}
}
Mat cameraMatrix, distCoeffs;
vector<Mat> rvecs, tvecs; calibrateCamera(objectPoints,imagePoints,Size(1280,1024),
cameraMatrix,distCoeffs,rvecs,tvecs);
FileStorage fs("calib_params.xml",FileStorage::WRITE);
fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;
fs.release(); return 0;
}注:需补充投影仪标定与立体标定步骤。
import numpy as npimport cv2
def calculate_phase(images): I0,I1,I2,I3=images return np.arctan2(I3-I1,I0-I2)
def unwrap_phase(wrapped_phase, gray_codes):
k=np.zeros_like(wrapped_phase,dtype=np.int32) for code in gray_codes:
k=(k<<1)|code return wrapped_phase+2*np.pi*k
phase_imgs=[cv2.imread(f"phase_{i}.png",0) for i in range(4)]
gray_imgs=[cv2.imread(f"gray_{i}.png",0)>128 for i in range(8)]
abs_phase=unwrap_phase(calculate_phase(phase_imgs),gray_imgs)注:需通过标定参数将绝对相位转换为三维点云。
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/registration/icp.h>
typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloudT;
int main() { PointCloudT::Ptr cloud1(new PointCloudT), cloud2(new PointCloudT); pcl::io::loadPCDFile("scan1.pcd",*cloud1); pcl::io::loadPCDFile("scan2.pcd",*cloud2);
pcl::StatisticalOutlierRemoval<PointT> sor;
sor.setInputCloud(cloud1);
sor.setMeanK(50);
sor.setStddevMulThresh(1.0);
sor.filter(*cloud1);
pcl::IterativeClosestPoint<PointT,PointT> icp;
icp.setInputSource(cloud1);
icp.setInputTarget(cloud2);
icp.setMaxCorrespondenceDistance(0.05);
icp.setMaximumIterations(100);
icp.setTransformationEpsilon(1e-8);
PointCloudT::Ptr aligned(new PointCloudT);
icp.align(*aligned); pcl::io::savePCDFile("aligned.pcd",*aligned); return 0;
}泊松表面重建命令:
PoissonRecon --in aligned.pcd --out mesh.ply --depth 10 --scale 1.1
后续处理:
网格优化:MeshLab 孔洞修复、边折叠简化
纹理映射:4096×4096 分辨率,UV 展开优化
|
误差来源 |
量级 |
控制方法 |
|---|---|---|
|
标定误差 |
0.01-0.1mm |
20-30 张标定图像;±0.001mm 精度标定板;每月重标定 |
|
相位误差 |
0.005-0.05mm |
提高投影仪对比度;四步以上相移;非线性校正 |
|
环境光干扰 |
0.02-0.2mm |
暗室环境(≤50lux);高亮度光源;优化曝光时间 |
|
表面反射影响 |
0.05-0.5mm |
喷涂≤0.05mm 厚哑光显像剂;多视角扫描 |
|
配准误差 |
0.01-0.1mm |
≥3 个非共面标记点;≥50% 重叠区域;点到面 ICP |
|
问题 |
根因 |
量化解决方案 |
|---|---|---|
|
点云噪声多 |
环境光 / 反光 |
环境光≤50lux;喷涂哑光剂;统计滤波标准差倍数 1.5 |
|
边缘毛刺 |
相位展开错误 |
格雷码位数≥10;质量图引导展开;去除边缘低质量像素 |
|
拼接错位 |
ICP 精度低 |
≥3 个标记点;重叠≥50%;点到面 ICP 替代点到点 |
|
网格不光滑 |
点云密度不均 |
增加扫描视角;MLS 平滑;泊松重建深度 11 |
|
纹理错位 |
不同步 |
硬件触发同步;曝光时间匹配投射时间;纹理坐标优化 |
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70049756/viewspace-3172383/ ,如需转载,请注明出处,否则将追究法律责任。