前言

一直就想做这样的专题,因为自己是一名算法工程师,而算法落地对算法工程师来说是职责和能力之体现,前面有一个专题是专门介绍Semi-Global Matching(SGM)双目立体匹配算法的理论知识,再做一个编码的专题我觉得算是一个必要的补充。话不多少,咱们开始吧!

注1:代码已实时更新到GitHub上,随着专题的完成,Github代码也将同步完成。仓库地址:https://github.com/ethan-li-coding/SemiGlobalMatching.git,感兴趣的话,点下star,有更新会实时通知到你的个人中心!)

注2:这是一个专题,我会一步步介绍SGM的实现,让读者循序渐进的掌握SGM编码实现

码上教学系列

【码上实战】【立体匹配系列】经典SGM:(1)框架与类设计
【码上实战】【立体匹配系列】经典SGM:(2)代价计算
【码上实战】【立体匹配系列】经典SGM:(3)代价聚合
【码上实战】【立体匹配系列】经典SGM:(4)代价聚合2
【码上实战】【立体匹配系列】经典SGM:(5)视差优化
【码上实战】【立体匹配系列】经典SGM:(6)视差填充
【码上实战】【立体匹配系列】经典SGM:(7)弱纹理优化

完整代码已发布于Github开源项目:Github/SemiGlobalMatching,欢迎免费下载

【码上实战】【立体匹配系列】经典SGM:(1)框架与类设计

  • 前言
  • 算法框架
  • SGM匹配类的设计
    • 编码环境
    • 设计步骤

算法框架

在编码前,我不能急于动手,先在脑中思考下算法模型,然后搭建一个大概的框架,无论框架是否合理,这一步我想是合理的,他是编码的指南,也是思维的整理和复习。我在编写了多年代码后,慢慢形成了这个习惯,这让我少走一些弯路,编码思路更清晰。

首先是SGM的算法步骤:

其次是代码的框架:

框架的解释:既然是基于C++,那肯定是以类的方式,咱们把SGM匹配当做一个对象类,匹配所需要的数据和参数为成员变量,匹配所执行的步骤当做成员函数,这里分为公有和私有,公有成员函数是调用层可调用的接口,我开放了三个:初始化、执行匹配以及重设置,这当然是调用层所喜欢的方式,完成功能的前提下调用越简单越好。具体的算法步骤我放到私有成员函数里,表示这些接口调用层不需要关心,而算法层需要包装成接口以让代码逻辑清晰而整洁。当然,如果是实际工作中,我的建议是:外面再以类的方式封装一个接口层,让调用者只能看到两个公有函数,其他全部看不见,这样可以做到算法保密,而简洁的接口调用者也用的方便舒心。(见此前博客C++学习 | C++ Implement的使用)

有了算法步骤和编码框架,我们就可以愉快的开始编码啦!

SGM匹配类的设计

前面我们确定了框架和步骤,这一步,当然趁热把类给设计出来,不管设计的好不好,后面我们可以即时修改,但是有一个初步的类设计,会让编码逻辑更清晰。

编码环境

系统:Windows 10
编码软件(IDE):Microsoft Visual Studio 2015(版本无所谓,不过建议2010以上,后面我简称VS)

设计步骤

第一步:打开VS,新建一个控制台工程(当然也可以新建动态库工程,不过一开始用控制台会更方便测试,后面可以随意修改成动态库工程),然后新建一个空的类:SemiGlobalMatching【头文件SemiGlobalMatching.h/源文件SemiGlobalMatching.cpp】。
如下所示:

头文件SemiGlobalMatching.h

#pragma once
class SemiGlobalMatching
{public:SemiGlobalMatching();~SemiGlobalMatching();
};

源文件SemiGlobalMatching.cpp

#include "stdafx.h"
#include "SemiGlobalMatching.h"SemiGlobalMatching::SemiGlobalMatching()
{}SemiGlobalMatching::~SemiGlobalMatching()
{}

第二步:在类中添加相关结构体、成员变量、成员函数。

在写功能性代码之前,我要为一些基本类型创建别名,如下:

typedef int8_t           sint8;      // 有符号8位整数
typedef uint8_t         uint8;      // 无符号8位整数
typedef int16_t         sint16;     // 有符号16位整数
typedef uint16_t        uint16;     // 无符号16位整数
typedef int32_t         sint32;     // 有符号32位整数
typedef uint32_t        uint32;     // 无符号32位整数
typedef int64_t         sint64;     // 有符号64位整数
typedef uint64_t        uint64;     // 无符号64位整数
typedef float           float32;    // 单精度浮点
typedef double          float64;    // 双精度浮点

它的主要用处是防止不同编译器或平台的基础类型位数不一致而需要大量修改代码(实际上这种情况很少见),以及便于理解。大家写代码不用这样的方式也OK。

好了,开始设计类吧!

首先是相关结构体,对一个算法来说,参数是一个很重要的部分,他是调用层可控的点之一,通过设置不同的参数,调用层可以得到不同的算法结果,研究人员希望参数可调性尽量高,而产品前端则希望参数在完成目标的情况下尽可能简洁。
我们为SGM设计了一个参数结构体,包含有5个参数,如下:

/** \brief SGM参数结构体 */
struct SGMOption {uint8 num_paths;      // 聚合路径数sint32  min_disparity;  // 最小视差sint32   max_disparity;  // 最大视差// P1,P2 // P2 = P2_int / (Ip-Iq)sint32  p1;                // 惩罚项参数P1sint32  p2_int;           // 惩罚项参数P2SGMOption(): num_paths(8), min_disparity(0), max_disparity(640), p1(10), p2_int(150) {}};

注释中含有参数的解释,后面在代码编写说明的过程中我也会进行解释。在此就不在赘述。

接下来看成员函数部分:

首先是公有成员函数,这部分是对调用端开放的:

第一个成员函数:初始化Initialize。有人肯定有疑问,为什么要加初始化呢,直接匹配不就完事了么?其实原因在于一方面算法需要有一些准备工作,比如参数初始化、加密检测之类的,有一个初始化接口可以逻辑上更加清晰,可以放入很多不适用于算法执行阶段做的工作;另一个很重要的原因是我们算法一般讲究效率,很多算法他都会有内存分配的操作,而有部分内存分配其实是可以复用的,比如说如果算法内部需要使用一个和影像等尺寸的标记位数组,用来标记像素的状态,而算法执行第一个像对和第二个像对其实都可以重复利用这个标记位数组,只要我们开始前把标记位清零就行,而如果我们不在初始化函数里预先开辟好,那么每次执行匹配都要重新开辟这块内存,无疑会造成效率降低。所以这形成了我的一个习惯,给算法都会加一个初始化步骤。
我们为SemiGlobalMatching类初始化函数设计的参数有影像高、宽、SGM参数。用来预分配可复用的内存块,及预设定SGM匹配的一些参数。

/*** \brief 类的初始化,完成一些内存的预分配、参数的预设置等* \param width        输入,核线像对影像宽* \param height    输入,核线像对影像高* \param option    输入,SemiGlobalMatching参数*/
bool Initialize(const uint32& width, const uint32& height, const SGMOption& option);

第二个成员函数:执行匹配Match。这个就不用多说了,它就是匹配类的核心了。
匹配函数的参数是左右影像的数据指针、输出的左影像视差图指针(需要预先开辟内存)。

/*** \brief 执行匹配* \param img_left        输入,左影像数据指针 * \param img_right        输入,右影像数据指针* \param disp_left     输出,左影像视差图指针,预先分配和影像等尺寸的内存空间*/
bool Match(const uint8* img_left, const uint8* img_right, float32* disp_left);

第三个成员函数:重设Reset。有同学可能有疑问,为啥放个这函数,原因是Initialize/Match方式虽然可以明显避免可复用内存的重复分配,当影像尺寸不变时(比如工程中只涉及到一种宽高影像)很有用,但是遇到影像尺寸会变化的项目时,就必须得提供接口来修改影像尺寸了,所以我们就加入了Reset,在影像尺寸或者匹配参数变化时重新做一些预备工作。

/*** \brief 重设* \param width     输入,核线像对影像宽* \param height    输入,核线像对影像高* \param option    输入,SemiGlobalMatching参数*/
bool Reset(const uint32& width, const uint32& height, const SGMOption& option);

其次是私有成员函数,这部分是不对调用端开放的,主要是SGM的子步骤函数:

/** \brief Census变换 */
void CensusTransform() const;/** \brief 代价计算     */
void ComputeCost() const;/** \brief 代价聚合     */
void CostAggregation() const;/** \brief 视差计算     */
void ComputeDisparity() const;/** \brief 一致性检查 */
void LRCheck() const;

最后是成员变量,这部分设计成私有类型对类的保护会更好,如果需要获取/设置某个成员的值,可以通过增加公有的成员函数get/set来获取或者设置,如下:

/** \brief SGM参数  */
SGMOption option_;/** \brief 影像宽     */
sint32 width_;/** \brief 影像高     */
sint32 height_;/** \brief 左影像数据  */
uint8* img_left_;/** \brief 右影像数据    */
uint8* img_right_;/** \brief 左影像census值 */
uint32* census_left_;/** \brief 右影像census值  */
uint32* census_right_;/** \brief 初始匹配代价 */
uint8* cost_init_;/** \brief 聚合匹配代价 */
uint16* cost_aggr_;/** \brief 左影像视差图    */
float32* disp_left_;/** \brief 是否初始化标志  */
bool is_initialized_;

好了,SemiGlobalMatching类的头文件就基本完成了,接下来就是各个函数实现了。

最终的头文件如下:

#pragma once
#include <cstdint>typedef int8_t          sint8;      // 有符号8位整数
typedef uint8_t         uint8;      // 无符号8位整数
typedef int16_t         sint16;     // 有符号16位整数
typedef uint16_t        uint16;     // 无符号16位整数
typedef int32_t         sint32;     // 有符号32位整数
typedef uint32_t        uint32;     // 无符号32位整数
typedef int64_t         sint64;     // 有符号64位整数
typedef uint64_t        uint64;     // 无符号64位整数
typedef float           float32;    // 单精度浮点
typedef double          float64;    // 双精度浮点class SemiGlobalMatching
{public:
SemiGlobalMatching();
~SemiGlobalMatching();/** \brief SGM参数结构体 */
struct SGMOption {uint8 num_paths;      // 聚合路径数sint32  min_disparity;  // 最小视差sint32   max_disparity;  // 最大视差// P1,P2 // P2 = P2_int / (Ip-Iq)sint32  p1;                // 惩罚项参数P1sint32  p2_int;           // 惩罚项参数P2SGMOption(): num_paths(8), min_disparity(0), max_disparity(64), p1(10), p2_int(150) {}};
public:
/*** \brief 类的初始化,完成一些内存的预分配、参数的预设置等* \param width       输入,核线像对影像宽* \param height    输入,核线像对影像高* \param option    输入,SemiGlobalMatching参数*/
bool Initialize(const uint32& width, const uint32& height, const SGMOption& option);/*** \brief 执行匹配* \param img_left       输入,左影像数据指针 * \param img_right        输入,右影像数据指针* \param disp_left     输出,左影像深度图指针,预先分配和影像等尺寸的内存空间*/
bool Match(const uint8* img_left, const uint8* img_right, float32* disp_left);/*** \brief 重设* \param width      输入,核线像对影像宽* \param height    输入,核线像对影像高* \param option    输入,SemiGlobalMatching参数*/
bool Reset(const uint32& width, const uint32& height, const SGMOption& option);private:/** \brief Census变换 */
void CensusTransform() const;/** \brief 代价计算     */
void ComputeCost() const;/** \brief 代价聚合     */
void CostAggregation() const;/** \brief 视差计算     */
void ComputeDisparity() const;/** \brief 一致性检查 */
void LRCheck() const;private:
/** \brief SGM参数     */
SGMOption option_;/** \brief 影像宽     */
sint32 width_;/** \brief 影像高     */
sint32 height_;/** \brief 左影像数据  */
uint8* img_left_;/** \brief 右影像数据    */
uint8* img_right_;/** \brief 左影像census值 */
uint32* census_left_;/** \brief 右影像census值  */
uint32* census_right_;/** \brief 初始匹配代价 */
uint8* cost_init_;/** \brief 聚合匹配代价 */
uint16* cost_aggr_;/** \brief 左影像视差图    */
float32* disp_left_;/** \brief 是否初始化标志  */
bool is_initialized_;
};

本章就介绍到这里,接下来我们将学习如何实现SGM立体匹配类的成员函数,让类真正起作用,并做一些匹配实验,直观的感受立体匹配的魅力。

理论恒叨系列

【理论恒叨】【立体匹配系列】经典SGM:(1)匹配代价计算之互信息(MI)
【理论恒叨】【立体匹配系列】经典SGM:(2)匹配代价计算之Census变换
【理论恒叨】【立体匹配系列】经典SGM:(3)代价聚合(Cost Aggregation)
【理论恒叨】【立体匹配系列】经典SGM:(4)视差计算、视差优化

博主简介:
Ethan Li 李迎松
武汉大学 摄影测量与遥感专业博士

主方向立体匹配、三维重建

2019年获测绘科技进步一等奖(省部级)

爱三维,爱分享,爱开源
GitHub: https://github.com/ethan-li-coding
邮箱:ethan.li.whu@gmail.com

个人微信:

欢迎交流!

喜欢博主的文章不妨关注一下博主的博客,感谢!
博客主页:https://ethanli.blog.csdn.net

【码上实战】【立体匹配系列】经典SGM:(1)框架与类设计相关推荐

  1. 【码上实战】【立体匹配系列】经典SGM:(2)代价计算

    码上教学系列 [码上实战][立体匹配系列]经典SGM:(1)框架与类设计 [码上实战][立体匹配系列]经典SGM:(2)代价计算 [码上实战][立体匹配系列]经典SGM:(3)代价聚合 [码上实战][ ...

  2. 【码上实战】【立体匹配系列】经典SGM:(4)代价聚合2

    昔人已乘黄鹤去,此地空余黄鹤楼. 2020对武汉.对中国.对世界来说是异常艰难的一年.武汉壮士扼腕,封一城而救一国,引得八方救援,举国抗疫.中国人在灾难面前总是空前团结,勇往直前!中华民族几千年来从未 ...

  3. 【码上实战】【立体匹配系列】经典SGM:(5)视差优化

    千呼万唤始出来,犹抱琵琶半遮面. 抱歉让大家久等,最近事儿繁多,导致更新推迟,实在抱歉. 码上教学系列 [码上实战][立体匹配系列]经典SGM:(1)框架与类设计 [码上实战][立体匹配系列]经典SG ...

  4. 【码上实战】【立体匹配系列】经典SGM:(6)视差填充

    科学是一个精益求精的过程. 码上教学系列 [码上实战][立体匹配系列]经典SGM:(1)框架与类设计 [码上实战][立体匹配系列]经典SGM:(2)代价计算 [码上实战][立体匹配系列]经典SGM:( ...

  5. 【码上实战】【立体匹配系列】经典SGM:(7)弱纹理优化

    欢迎收看长篇小说SGM第七章:弱纹理优化 码上教学系列 [码上实战][立体匹配系列]经典SGM:(1)框架与类设计 [码上实战][立体匹配系列]经典SGM:(2)代价计算 [码上实战][立体匹配系列] ...

  6. 【码上实战】【立体匹配系列】经典AD-Census: (1)框架

    下载AD-Census完整源码,点击进入: https://github.com/ethan-li-coding/AD-Census 欢迎同学们在Github项目里讨论,如果觉得博主代码质量不错,右上 ...

  7. 【码上实战】【立体匹配系列】经典AD-Census: (6)多步骤视差优化

    同学们好久不见! 下载完整源码,点击进入: https://github.com/ethan-li-coding/AD-Census 欢迎同学们在Github项目里讨论! 在实战的上一篇,我们对AD- ...

  8. 【码上实战】【立体匹配系列】经典PatchMatch: (1)框架

    下载完整源码,点击进入: https://github.com/ethan-li-coding/PatchMatchStereo 欢迎同学们在Github项目里讨论,如果觉得博主代码质量不错,右上角s ...

  9. 【码上实战】【立体匹配系列】经典PatchMatch: (4)代价计算

    下载完整源码,点击进入: https://github.com/ethan-li-coding/PatchMatchStereo 欢迎同学们在Github项目里讨论,如果觉得博主代码质量不错,右上角s ...

最新文章

  1. 工业4.0进行时:增强现实技术如何变革制造业
  2. [渝粤教育] 西南科技大学 管理信息系统 在线考试复习资料(1)
  3. 大数据之-入门_大数据部门业务流程分析---大数据之hadoop工作笔记0006
  4. java动物类_使用java面向对象创建动物类并输出动物信息
  5. AAuto如何设置定时器
  6. A-AUTOバッチ管理ツール(HOLD之后,如何再次启动)
  7. 【CAIL2021】任务① | 阅读理解
  8. mysql 5.7.16 忘记root 密码 如何修改root密码
  9. Jenkins配置slaver节点
  10. Redis概述、安装、可视化访问
  11. 小帅一点资讯小程序源码
  12. 纵观Android Adapter适配器。比较三种常用Adapter(SimpleAdapter,ArrayAdapter,BaseAdapter)。
  13. 海湾汉字编码表全部_汉字unicode码表范围和常用汉字unicode码
  14. jqGrid学习笔记(一)
  15. MATLAB中判断两字符串是否相等
  16. 重走Android路 之 挑几个基本控件玩玩(上卷)
  17. win7无线手柄测试软件,win7系统设置和调试游戏手柄的方法
  18. 解决COM组件80070005错误
  19. 钱宝事件,是什么让你们如此疯狂呢?
  20. vue div自适应高度

热门文章

  1. 1201:8003 撬锁者
  2. 机器学习和数据分析-pandas从数据源中导入数据
  3. ssm基于微信小程序的社区老人健康管理服务系统的设计与实现 毕业设计-附源码011513
  4. Linux命令`ll`的结果解析
  5. SCI论文参考文献(bib文件)格式
  6. matlab 线圈 互感,任意空间位置线圈的互感计算方法.PDF
  7. 联发科建MM,应用商店成菜市场
  8. 进销存软件开发视频教程(C#版,共70讲)
  9. PHP契约类的作用,LOL混沌契约作用详解 LOL混沌契约是什么
  10. html5 flash 游戏,使用Flash CC的HTML5 Canvas游戏中的HitTest