
1. 项目速览:给牲畜体表做一次“数字体检”📏
牲畜体表信息三维重建系统面向畜牧业中的体尺测量、体型评估与数字化建模需求,基于 Astra Pro RGB-D 相机获取牲畜体表的深度、RGB 与点云数据,再通过点云配准、姿态归一化和模板非刚性配准,得到可用于分析的三维体表模型。
这套系统的核心价值不在于“拍得好看”,而在于把原本依赖人工皮尺、经验观察和静态记录的体表测量,转换为可复现、可计算、可保存的三维数据流程。换句话说,它让牲畜体型从“看起来挺壮”变成“点云里有证据”。😄
| 项目要素 | 内容 |
|---|---|
| 项目来源 | 第一届 3D 视觉创新应用竞赛参赛选手陆杰 |
| 相机型号 | Astra Pro |
| 支持设备 | Zora P1 开发板 |
| 核心任务 | 采集牲畜体表数据,并重建三维表面模型 |
| 主要技术 | OpenNI、OpenCV、PCL、MATLAB、L-BFGS-B、非刚性模板配准 |
| 主要输出 | 深度图、RGB 图、彩色点云、配准点云、非刚性重建网格 |
| 源代码链接 | 3DReconstruction_Animal-body |
2. 系统链路:先采集,再拼接,最后“塑形”🧩

工作主要分成五个部分:环境配置、数据获取、数据配准、姿势归一化和三维重建。代码目录也与这一逻辑基本对应,整体上是一条从传感器数据到体表模型的完整链路。
| 阶段 | 输入 | 处理 | 输出 |
|---|---|---|---|
| 环境配置 | Zora P1、Astra Pro | 配置 OpenNI 与 OpenCV | 可运行采集环境 |
| 数据获取 | 深度流、RGB 流 | 保存深度图、RGB 图和彩色点云 | PNG、PLY |
| 数据配准 | 左右侧点云 | 同名点估计刚性变换 | 拼接点云 |
| 姿态归一化 | 分割后的牲畜点云 | 统一方向和坐标系 | 规范化点云 |
| 三维重建 | 模板网格、扫描点云、特征点 | 刚性初始化和非刚性变形 | 重建网格 |
| 目录/文件 | 作用 | 可读出的信息 |
|---|---|---|
牲畜体表信息三维重建.pdf |
项目说明文档 | 五阶段技术路线、实验结果 |
第二部分代码/createDepthImg |
深度图采集 | OpenNI 读取深度流并保存 PNG |
第二部分代码/saveRGBImg |
RGB 图采集 | OpenCV UVC 接口读取彩色图 |
第二部分代码/createRGBpointcloud |
彩色点云生成 | 深度反投影为 XYZ,并写入 RGB |
第三部分代码/transform_twoside |
点云配准 | PCL 读取 PLY,SVD 估计变换矩阵 |
第四部分代码/姿势归一化软件地址.txt |
姿态归一化工具 | 指向 LSSA 牲畜点云分析软件 |
第五部分代码/humanshape |
模板拟合框架 | 基于人体形体模型代码改造到猪体 |
3. 数据采集:Astra Pro 先把“表面轮廓”看清楚📷

采集端的核心工作分为两条流:深度流由 OpenNI 获取,RGB 流由 OpenCV 的 VideoCapture 获取。项目中保留了 DepthImage.png 与 saved_img.png 等结果文件,说明采集链路已经跑通。
深度图保存逻辑来自 createDepthImg/main.cpp。它先打开设备并启动深度流,再把 16 位深度帧转换为 OpenCV 矩阵,最后缩放成 8 位灰度图保存,方便观察距离层次。
// 节选自 createDepthImg/main.cpp
rc = depth.create(device, openni::SENSOR_DEPTH);
if (rc == openni::STATUS_OK)
rc = depth.start();
rc_d = depth.readFrame(&frameDepth);
pDepth = (DepthPixel*)frameDepth.getData();
const cv::Mat mImageDepth(
frameDepth.getHeight(),
frameDepth.getWidth(),
CV_16UC1,
(void*)frameDepth.getData()
);
cv::Mat mScaledDepth;
int iMaxDepth = depth.getMaxPixelValue();
mImageDepth.convertTo(mScaledDepth, CV_8U, 255.0 / iMaxDepth);
cv::imwrite("DepthImage.png", mScaledDepth);
这里有一个很朴素但关键的设计:深度原始数据适合计算,灰度深度图适合观察。前者保留毫米级深度信息,后者帮助开发者判断传感器是否正确捕获到了场景结构。
RGB 图像采集则通过 UVC 摄像头接口完成:
// 节选自 saveRGBImg/main.cpp
VideoCapture videoCapture;
videoCapture.open(cv::CAP_ANY);
videoCapture.set(CV_CAP_PROP_FOURCC, CV_FOURCC('M', 'J', 'P', 'G'));
videoCapture.set(CV_CAP_PROP_FRAME_WIDTH, 640);
videoCapture.set(CV_CAP_PROP_FRAME_HEIGHT, 480);
for (int i = 0; i < 10; i++)
{
Mat frame;
videoCapture >> frame;
imwrite("/home/orbbec/Desktop/saveRGBImg/Bin/Arm64-Release/saved_img.png", frame);
}
| 数据类型 | 采集接口 | 分辨率/格式 | 用途 |
|---|---|---|---|
| 深度图 | OpenNI depth stream | 16 位深度,转 8 位预览 | 反投影和结构分析 |
| RGB 图 | OpenCV VideoCapture |
640×480,MJPG | 给点云附加颜色 |
| 彩色点云 | 深度 + RGB | PLY 顶点带 RGB | 后续配准和重建 |
4. 彩色点云:把每个像素“拎”到三维空间里🌈

项目中的 createRGBpointcloud/main.cpp 负责将深度图转换成三维坐标,并把 RGB 颜色写入 PLY 文件。其本质是相机模型的反投影:给定像素坐标 (u, v)、深度 z 和相机内参 (fx, fy, cx, cy),即可计算空间坐标。
// 节选自 createRGBpointcloud/main.cpp
float fdx = g_IntrinsicParam.f_x * ((float)(width) / RESOULTION_X);
float fdy = g_IntrinsicParam.f_y * ((float)(height) / RESOULTION_Y);
float u0 = g_IntrinsicParam.c_x * ((float)(width) / RESOULTION_X);
float v0 = g_IntrinsicParam.c_y * ((float)(height) / RESOULTION_Y);
float tx = (u - u0) / fdx;
float ty = (v - v0) / fdy;
world_x = depth * tx;
world_y = depth * ty;
world_z = depth;
fprintf(fp, "%f %f %f %d %d %dn",
world_x, world_y, world_z,
color_r, color_g, color_b);
这段代码对应的数学形式可以写成:
x = (u - cx) * z / fx
y = (v - cy) * z / fy
z = depth
point = [x, y, z, r, g, b]
项目生成的 pointcloud_1.ply 是 ASCII PLY,文件头显示包含 264772 个有效顶点,每个点都带有 x, y, z, red, green, blue。这意味着它不是只有几何形状的“灰模”,而是带有体表颜色信息的点云,更适合后续观察与人工标注。
| PLY 字段 | 含义 | 作用 |
|---|---|---|
x, y, z |
三维坐标 | 表达牲畜体表几何 |
red, green, blue |
彩色信息 | 辅助观察与标注 |
vertex count |
有效点数量 | 反映采集质量与过滤结果 |
depth range |
20 mm 到 4000 mm | 排除无效或过远点 |
5. 双侧点云配准:左边一片,右边一片,合体才像样🧷

牲畜体表通常不是一次单侧扫描就能完整获得。项目说明中提到,系统获取两侧点云数据后进行配准。代码中使用 PCL 读取多个 PLY 文件,通过同名点估计刚性变换矩阵,再把其中一侧点云变换到另一侧坐标系下。
// 节选自 transform_twoside.cpp
pcl::io::loadPLYFile(argv[1], *cloud_in);
pcl::io::loadPLYFile(argv[2], *cloud_in2);
pcl::io::loadPLYFile(argv[3], *cloud_in3);
*cloud_in2 += *cloud_in3;
*cloud_in3 = *cloud_in2;
pcl::registration::TransformationEstimationSVD<PointT, PointT> TESVD;
pcl::registration::TransformationEstimationSVD<PointT, PointT>::Matrix4 transformation2;
TESVD.estimateRigidTransformation(*corrspond_in, *corrspond_out, transformation2);
pcl::transformPointCloud(*cloud_in, *show_transformed_clouds, transformation2);
*show_transformed_clouds += *cloud_in2;
pcl::PCDWriter writer;
writer.write("registered_final2.pcd", *show_transformed_clouds, false);
pcl::PLYWriter writer2;
writer2.write("registered_final2.ply", *show_transformed_clouds, false);
这里的关键不是 ICP 大循环,而是先用同名点做一个可解释的刚性初始化。代码里手动给出了两组对应点,之后用 SVD 估计旋转和平移。这种方法对数据量要求低,特别适合项目早期验证:只要同名点选得稳,拼接效果就不会太离谱。
| 配准要素 | 项目实现 | 解读 |
|---|---|---|
| 输入点云 | 多个 PLY 文件 | 来自牲畜不同侧面的采集 |
| 对应关系 | 手动选取同名点 | 保证几何约束明确 |
| 变换估计 | SVD 刚性配准 | 求解旋转和平移 |
| 输出结果 | registered_final2.pcd、registered_final2.ply |
用于后续归一化和重建 |
6. 姿势归一化:先把“站姿”摆正,再谈重建🧭

牲畜扫描数据会受到站姿、相机角度、左右侧扫描顺序等因素影响。如果直接把这些点云送入模板拟合,算法很可能把“姿态差异”误当成“体型差异”。所以项目在三维重建前加入姿势归一化步骤。
项目中引用了 LSSA_CAU 相关方法,并在 姿势归一化软件地址.txt 中给出了软件地址:
https://github.com/LiveStockShapeAnalysis/LiveStockShapeAnalysis-Software/releases/tag/v1.7
归一化的目标可以概括为三件事:
| 处理目标 | 具体含义 | 对重建的帮助 |
|---|---|---|
| 分割目标 | 从背景中分离牲畜体表点云 | 降低噪声干扰 |
| 统一方向 | 让头尾、左右、上下方向一致 | 方便模板对齐 |
| 统一坐标 | 将点云放入稳定坐标系 | 提高非刚配准收敛性 |
如果把非刚性配准比作给模板“穿衣服”,那么姿势归一化就是先让模板和扫描对象站到同一个坐标舞台上。舞台都没对齐,后面的变形就容易开始自由发挥,最后模型看起来会很努力,但不一定很正确。😄
7. 模板非刚性配准:让标准猪体模型贴合扫描数据🧠

项目第五部分参考了 Leonid Pishchulin 等人的 3D Human Shape Modeling 方法,并将人体形体模板拟合框架迁移到牲畜三维重建场景。项目说明中写明:模板数据为普通猪的网格数据,扫描数据为姿势归一化后的猪体点云,并在模板和扫描数据上分别选取 37 个对应特征点。
整体过程分为两步:
| 步骤 | 方法 | 目的 |
|---|---|---|
| 刚性初始化 | 基于特征点的模板到扫描刚性配准 | 让模板先大致贴近点云 |
| 非刚性变形 | 每个模板点拥有 4×4 变换矩阵 | 让模板表面逐步贴合扫描体表 |
入口脚本 test.m 会读取测试目录中的 PLY 和标志点文件,然后调用 testFitMesh:
% 节选自 humanshape/test.m
expidx = 0;
p = expParams(expidx);
Path = [p.rootDir 'pig_test/orbbec_test/'];
scanFiles = dir(fullfile(Path,'*.ply'));
landmarkFiles = dir(fullfile(Path,'*.lnd'));
landmarkXML = dir(fullfile(Path,'*.xml'));
landmarkIdx = dir(fullfile(Path,'*.txt'));
assert(length(scanFilenames) == length(landmarkFilenames));
fprintf('register test scann');
testFitMesh(scanFilenames, landmarkFilenames, expidx);
参数文件 expParams.m 中可以看到项目已经将原始人体模型流程改成了猪体测试流程:
% 节选自 fitting/expParams.m
case 0
p.landmarksSM = {[p.rootDir 'pig_test/orbbec_LandmarkIdxs_2.txt']};
p.pathSM = [p.rootDir 'pig_test/pig_orbbec.ply'];
p.name = 'test-nrd-3';
p.fitDir = [p.expDir '/' p.name];
p.initDir = p.fitDir;
p.nrdWidx = 3;
p.nPCA = 20;
其中 pig_orbbec.ply 是猪体模板,文件头显示包含 26238 个顶点和 52476 个三角面;orbbec_test/1_pig_sup_2.ply 是测试扫描点云,文件头显示包含 11876 个顶点,并带有 RGB 和 alpha 字段。
8. 非刚性优化:数据项、平滑项、特征点项三方拉扯⚖️

非刚性配准的核心在 test_NRD.m、NRDFunction.m 与 NRDgradientFunction.m。项目为模板的每个顶点维护一个 4×4 变换矩阵,优化目标由三类约束组成:
| 约束项 | 对应代码变量 | 作用 |
|---|---|---|
| 数据项 | E_DATA |
模板点贴近扫描点云最近邻 |
| 平滑项 | E_SMOOTH |
相邻模板点的变换不要突变 |
| 特征点项 | E_LM |
保证关键体尺位置对齐 |
NRDFunction.m 中的目标函数非常直观:模板既要靠近扫描点,也要保持表面连续,还要尊重人工选取的特征点。也就是说,它不是“哪里有点就往哪里冲”,而是“贴近,但别把身体拧成奇怪形状”。严谨一点说,这是一种带正则化的非刚性配准问题。
% 节选自 fitting/NRDFunction.m
for i = 1:NUM_LM
E_LM_Gen = E_LM_Gen + ...
(norm(A_LM(:,:,i) * LM_TL(:,:,i) - LM_SC(:,:,i)))^2;
end
for i = 1:NUM_TL_ALL
NNvertSMPoint = NN_VERT_TL{TL_ALL_IDX(1,i),1};
for j = 1:size(NNvertSMPoint,2)
E_SMOOTH_Gen = E_SMOOTH_Gen + ...
norm(A(:,:,TL_ALL_IDX(1,i)) - A(:,:,NNvertSMPoint(j)),'fro')^2;
end
end
for i = 1:NUM_TL_POINTS
E_data_total = E_data_total + ...
IS_VALID_NN(i,1) * CONF_SC_POINTS(i,1) * ...
(norm(A_DATA(:,:,i) * TL_POINTS(:,:,i) - [NN_SC(i,:) 1]'))^2;
end
E_TOTAL = E_DATA + E_SMOOTH + E_LM;
test_NRD.m 中还设置了权重逐步降低的迭代策略。初期平滑项权重大,先保证模板整体稳定;随后逐步降低平滑约束,让模板更贴近真实扫描。这个策略有点像捏模型:先捏大形,再修细节,别一上来就抠局部,否则容易捏歪。
% 节选自 pig_test/test_NRD.m
W_DATA = 1;
W_SMOOTH_HEAD = 1e+6;
W_SMOOTH_HANDS = 1e+6;
W_SMOOTH_GEN = 1e+6;
W_LM = 0.1;
while (abs(errPrev - err) > eps_err && W_SMOOTH_GEN >= 1*1e+3)
pointsIdxsScanNN = getNNheadAndBody(scan, template);
Amatrix = lbfgsb(Amatrix, lowerbound, upperbound, ...
'NRDFunction', 'NRDgradientFunction');
W_SMOOTH_GEN = W_SMOOTH_GEN * wrSmoothBody;
W_SMOOTH_HANDS = W_SMOOTH_HANDS * wrSmoothHand;
W_SMOOTH_HEAD = W_SMOOTH_HEAD * wrSmoothHead;
W_LM = W_LM * wrLandmarks;
end
9. 实验结果:误差有数,模型有形📊

项目文档给出了非刚性配准结果与原始点云之间的定量比较。平均距离误差约为 1.1497 mm,最大距离误差为 23.1323 mm,最小距离误差为 0.0014 mm。从指标看,模板拟合在整体上能够较贴近扫描点云,但局部极值仍提示边缘、遮挡、噪声或特征点区域可能存在偏差。
| 评价指标 | 数值 | 单位 | 解读 |
|---|---|---|---|
| 平均距离误差 | 1.1497 | mm | 整体贴合精度较高 |
| 最大距离误差 | 23.1323 | mm | 局部可能有遮挡或配准偏差 |
| 最小距离误差 | 0.0014 | mm | 部分区域几乎重合 |
项目目录中还保留了若干结果文件:
| 结果文件 | 位置 | 说明 |
|---|---|---|
DepthImage.png |
createDepthImg/Bin/Arm64-Release |
深度图预览 |
saved_img.png |
saveRGBImg/Bin/Arm64-Release |
RGB 图像样例 |
pointcloud_*.ply |
createRGBpointcloud/Bin/Arm64-Release |
多帧彩色点云 |
pig_orbbec.ply |
humanshape/pig_test |
猪体模板网格 |
1_pig_sup_2.ply |
humanshape/pig_test/orbbec_test |
测试扫描点云 |
NRD*.mat |
experiments/test-nrd-3/1_pig_sup_2 |
非刚性配准迭代结果 |
10. 为什么这套方案适合牲畜体表信息重建?🐾
牲畜体表重建不是普通室内场景重建。它的对象具有相似形态、局部曲面连续、姿态变化有限但不能忽略,并且实际测量往往更关心体尺、轮廓、局部表面而非完整纹理。因此,项目采用“点云采集 + 模板约束”的路线是合理的。
| 技术选择 | 对牲畜场景的意义 |
|---|---|
| RGB-D 采集 | 低成本获取体表几何与颜色 |
| 双侧点云 | 降低单视角遮挡问题 |
| 同名点配准 | 小规模数据也能完成可控拼接 |
| 姿态归一化 | 减少站姿差异带来的误差 |
| 模板非刚性拟合 | 让稀疏或缺失区域获得形体先验 |
| 特征点约束 | 保证关键体尺位置更稳定 |
如果没有模板,点云缺失处只能“空着”;如果只有模板,没有扫描数据,又容易变成“平均牲畜”。项目的做法相当于把二者结合起来:扫描数据给真实个体,模板模型给结构先验。一个负责实事求是,一个负责别让形状跑偏。🙂
11. 工程环境:小板子、老接口、硬核依赖,一个都不能少🧰

| 环境/组件 | 用途 | 备注 |
|---|---|---|
| Zora P1 | 嵌入式采集端 | 用于连接 Astra Pro |
| Astra Pro | RGB-D 数据来源 | 提供深度与彩色信息 |
| OpenNI2 | 深度流读取 | 需配置 SDK 和驱动 |
| OpenCV | RGB 读取、图像保存 | Makefile 中加入 pkg-config opencv |
| PCL | 点云读取、配准、保存 | 用于 SVD 变换和 PCD/PLY 输出 |
| MATLAB | 模板拟合与优化 | 运行 humanshape 改造代码 |
| L-BFGS-B | 非刚性优化 | 用于求解变形矩阵 |
项目中的 Makefile 明确把 OpenCV 编译参数加入构建流程:
# 节选自 CommonCppMakefile
CFLAGS += ${shell pkg-config opencv --cflags}
LDFLAGS += ${shell pkg-config opencv --libs}
点云配准部分则使用 CMake 寻找 PCL:
# 节选自 transform_twoside/CMakeLists.txt
find_package(PCL 1.5 REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
add_executable(transform_twoside transform_twoside.cpp)
target_link_libraries(transform_twoside ${PCL_LIBRARIES})
12. 当前局限:它已经能重建,但还可以更“聪明”一点🛠️
| 局限 | 表现 | 改进方向 |
|---|---|---|
| 依赖手动同名点 | 配准质量受人工选点影响 | 引入自动特征点或粗配准 |
| 双侧扫描有限 | 顶部、腹部等区域可能缺失 | 增加多视角采集 |
| RGB-D 对齐较简化 | 彩色与深度可能存在偏差 | 做精确内外参标定 |
| 姿态归一化依赖外部工具 | 流程自动化程度不足 | 集成归一化模块 |
| 模板迁移成本较高 | 需要特征点和猪体模板 | 建立不同品种模板库 |
| MATLAB 依赖较重 | 部署不够轻量 | 改写为 Python/C++ 优化流程 |
13. 后续优化路线:从“能重建”走向“能测量、能分析、能上线”🚀

| 优化方向 | 具体做法 | 预期收益 |
|---|---|---|
| 多视角采集 | 增加前后、顶部或环绕视角 | 减少遮挡与缺失 |
| 自动特征点 | 用几何曲率或学习方法检测关键点 | 降低人工标注成本 |
| 鲁棒配准 | 结合 RANSAC、ICP 和全局特征 | 提升拼接稳定性 |
| 自动分割 | 从背景中自动提取牲畜点云 | 提高系统易用性 |
| 体尺测量 | 从重建模型提取体长、体高、胸围等 | 直接服务养殖管理 |
| 模板库建设 | 按品种、体型建立统计模型 | 提升泛化能力 |
| 轻量部署 | 将拟合流程工程化为独立服务 | 便于实际落地 |
14. 总结:这不是“给牲畜拍照”,而是在建立可计算的体表档案🎯
牲畜体表信息三维重建系统把 Astra Pro、Zora P1、OpenNI、OpenCV、PCL 和模板非刚性配准串成了一条完整技术路线。它从深度图和彩色图出发,生成体表点云;再用同名点和 SVD 完成多侧点云配准;随后通过姿态归一化和非刚性模板拟合,得到更完整、更可分析的三维体表模型。
从学术与工程角度看,这个项目的亮点在于它没有停留在“获取点云”这一步,而是继续向体表结构表达、误差评价和形体建模推进。对畜牧业来说,这类系统未来可以进一步支撑自动体尺测量、育种评估、健康监测和数字化养殖管理。简单说,就是让牲畜体表信息从“人工经验”升级为“空间数据”。📐