1. 写在前面
在前面的博客中,介绍了如何通过 C/C++ 控制我们的2D相机和投影仪。在这篇博客中,博主将教给大家如何通过 C/C++ 开发我们的结构光3D相机SDK。完成本篇博客的学习内容后,你将收获如何编写自己的结构光3D相机的SDK开发经验。
本系列博客的完整项目代码皆位于博主的Github项目SLMaster👈中:
https://github.com/Practice3DVision/SLMaster
动动你的小指头给个Star⭐并follow博主吧!你的支持是博主不懈的动力!
2. 通过Json控制的结构光相机SDK开发
经过前两篇博客的内容,大家肯定都对简单工厂模式比较熟悉了。在结构光相机里,我们也使用这种模式,所以我们的UML图长成这样:
这样的话,我们就能在单目、双目、多目结构光3D相机中无缝切换了!
由于代码部分占篇幅比较长,所以博主就不再放代码了,而是讲解必要的代码片段,相信这种方式能让博客更简洁!同学们可以在我的Github上下载完整代码,别忘了给个⭐喔。
随之而来的问题是,我们如何去控制我们的结构光3D相机参数呢?我们知道算法可能具有很多的参数,如果在算法运行时逐一修改参数并试运行,那工作量有点大而且繁琐(你可不想每次调参都编译链接再运行一遍吧)。
我们可以通过外部文件来控制我们的相机。博主在这里使用的是Json文件,如果你有兴趣也可以使用.ini配置文件等等。
Json文件的后缀为 .json ,它其实也是一种文本文件。我们使用它来记录有关于我们结构光3D相机的所有参数,包括3D相机名称、开发者、3D相机性能参数、内部2D相机硬件配置、内部投影仪配置、算法控制参数等等。
如此一来,作为开发者,我们最了解我们开发的这款相机的参数配置,所以可以写出完整的Json配置文件用于描述我们的结构光3D相机;而作为使用者,我们可能对如何通过SDK调参不感兴趣,那我们就可以通过拿到Json配置文件获得这款结构光3D相机的所有信息,并仅需在该文件中修改相应参数即可完整整个结构光3D相机的参数更新,而不需要重新编译可执行文件等复杂操作。
博主在这里给出我们的三目结构光相机Json文件的部分代码:
{
"camera" : {
"algorithm" : [
{
"data" : 1,
"title" : "Gpu Accelerate",
"type" : "bool"
},
{
"data" : 3.0,
"title" : "Phase Shift Times",
"type" : "number"
},
{
"data" : 3000.0,
"title" : "Exposure Time",
"type" : "number"
},
...
],
"device" : [
{
"data" : "Trinocular Camera",
"title" : "Camera Name",
"type" : "string"
},
...
]
}
}
是不是很明了,一眼就能知道该款结构光3D相机的类型和可供修改的算法参数等。
那如何实现这种需求呢?首先,我们通过jsoncpp库来实现 C++ 下的 json文件读取与写入。然后在我们的结构光相机类(如上述的TrinocularCamera
)初始化时,就读入该配置,在类析构时将所有的参数重新写入json文件中(为什么不每次一更新就写入呢?磁盘写入操作耗时啊!)。
博主给出一些源代码看看:
TrinocularCamera::TrinocularCamera(IN const std::string jsonPath)
: SLCamera(jsonPath), jsonPath_(jsonPath), isInitial_(false), isCaptureStop_(true) {
if (loadParams(jsonPath, jsonVal_)) {
if (booleanProperties_["Gpu Accelerate"]) {
#ifndef WITH_CUDASTRUCTUREDLIGHT_MODULE
std::clog << "lib isn't build with cuda, we will disable it!" << std::endl;
booleanProperties_["Gpu Accelerate"] = false;
#endif //!WITH_CUDASTRUCTUREDLIGHT_MODULE
}
isInitial_ = true;
}
}
TrinocularCamera::~TrinocularCamera() {
updateCamera();
}
逻辑还是很简单的吧!
接下来是我们的结构光3D相机类的具体实现了!我们知道结构光3D相机最主要的功能和2D相机类似,只不过数据结构变了,所以我们可以归纳为这么几个主要功能:
- 相机在线状态查询
- 相机连接与断开
- 捕获(执行三维重建)
- 相关的属性读取写入以及接口等
1)相机在线查询
我们在TrinocularCamera
类中使用组合模式加入我们前两篇博客中所写的相机工厂类CameraFactory
与投影仪工厂类ProjectorFactory
。然后通过读取Json文件中的硬件描述,获得对应的2D相机指针和投影仪指针,并查询它们是否在线,如果在线,我们就返回结构光相机在线true
(为了使用方便,我们还将深度2D相机的内参进行返回),否则,返回不在线false
。源代码如下:
SLCameraInfo TrinocularCamera::getCameraInfo() {
const device::camera::CameraFactory::CameraManufactor manufator =
stringProperties_["2D Camera Manufactor"] == "Huaray"
? device::camera::CameraFactory::Huaray
: device::camera::CameraFactory::Halcon;
auto pLeftCamera = cameraFactory_.getCamera(
stringProperties_["Left Camera Name"], manufator);
auto pRightCamera = cameraFactory_.getCamera(
stringProperties_["Right Camera Name"], manufator);
auto pColorCamera = cameraFactory_.getCamera(
stringProperties_["Color Camera Name"], manufator);
auto leftCameraInfo = pLeftCamera->getCameraInfo();
auto rightCameraInfo = pRightCamera->getCameraInfo();
auto colorCameraInfo = pColorCamera->getCameraInfo();
auto pProjector =
projectorFactory_.getProjector(stringProperties_["DLP Evm"]);
device::projector::ProjectorInfo projectorInfo = pProjector ? pProjector->getInfo() : device::projector::ProjectorInfo();
if(!pProjector) {
projectorInfo.isFind_ = false;
}
SLCameraInfo slCameraInfo;
slCameraInfo.isFind_ = leftCameraInfo.isFind_ &&
rightCameraInfo.isFind_ && colorCameraInfo.isFind_ && projectorInfo.isFind_;
slCameraInfo.cameraName_ =
slCameraInfo.isFind_ ? stringProperties_["Camera Name"] : "NOT_FOUND";
slCameraInfo.intrinsic_ = slCameraInfo.isFind_
? caliInfo_->info_.M1_
: cv::Mat::zeros(3, 3, CV_32FC1);
return slCameraInfo;
}
2)相机连接与断开
与上述类似,我们通过调用底层2D相机与投影仪接口实现结构光3D相机的连接与断开。断开连接的源码如下:
bool TrinocularCamera::disConnect() {
const device::camera::CameraFactory::CameraManufactor manufator =
stringProperties_["2D Camera Manufactor"] == "Huaray"
? device::camera::CameraFactory::Huaray
: device::camera::CameraFactory::Halcon;
const bool disConnectLeftCamera =
cameraFactory_
.getCamera(stringProperties_["Left Camera Name"], manufator)
->disConnect();
const bool disConnectRightCamera =
cameraFactory_
.getCamera(stringProperties_["Right Camera Name"], manufator)
->disConnect();
const bool disConnectColorCamera =
cameraFactory_
.getCamera(stringProperties_["Color Camera Name"], manufator)
->disConnect();
const bool disConnectProjector =
projectorFactory_.getProjector(stringProperties_["DLP Evm"])
->disConnect();
return disConnectLeftCamera && disConnectRightCamera && disConnectColorCamera && disConnectProjector;
}
3)捕获(执行三维重建)
这一点就比较有意思了,我们知道我们的结构光3D相机是可以使用很多种算法的,比如,你可能在面对高反物体时,使用多次曝光算法进行三维重建;而在面对工况良好的场景下,使用相移互补格雷码解码算法,等等。所以,博主并不写死算法,而是创建一个纯虚基类Pattern
作为算法基类,所有算法继承并实现其接口。然后在结构光3D相机类SLCamera
中使用设计模式中的组合模式,组合一个Pattern*
成员,并提供可供修改当前算法的公开接口(其实就是把具体选择什么算法延迟了)。部分源码如下:
/**
* @brief 设置条纹编解码方法
* @param pattern_ 条纹编解码方法
* @return
*/
void setPattern(Pattern* pattern) { pattern_ = pattern; }
当我们进行三维重建时,我们仅需调用pattern_
的decode()
接口即可。源码参考如下:
pattern_->decode(imgs, depthMap, booleanProperties_["Gpu Accelerate"]);
是不是很简洁明了呢?
3. 效果
我们来看看在软件中,这部分代码的具体效果体现在哪里。
编号 | 图片 |
---|---|
1 | ![]() |
2 | ![]() |
3 | ![]() |
4. 总结
在这篇博客中,博主介绍了如何使用 C/C++ 开发结构光3D相机的 SDK ,在下一小节中,博主将开始介绍算法与代码实现。
本系列文章将持续更新,如果有等不及想要获得代码的小伙伴,可访问博主Github中的SLMaster项目,动动你的小手指,follow and star⭐!你们的关注是博主持续的动力!
Github:https://github.com/Practice3DVision
QQ群:229441078
公众号:实战3D视觉