System Status: 2 IR cameras + 1 pair laser projector
Using Active Stereo method, not coded structure light( coding and decoding) or ToF.
1. OpenCV Stereo BM/SGBM
BM算法原理简介
Step1.使用立体相机标定参数对当前StereoCamera出图进行校准,校准后的同一目标点处于相同的水平线上,校准后的图像为left_rectify& right_rectify;
Step2.图像预滤波,使用水平sobel算子对图像进行处理,产生新的滤波后图像P_new
Step3.计算SAD(sum ofabsolute difference) cost,上述代价是在SAD窗口中计算得到;
Step4.唯一性检验:视差窗口范围内最低代价是次低代价的(1+ uniquenessRatio/100)倍时,最低代价对应的视差值才是该像素点的视差,否则该像素点处视差为0;
Step5.左右一致性检验:左图找到的匹配点,与匹配点在左图中的匹配点为同一点才判断合法,否则为误匹配点。
StereoBM参数说明:
预处理参数:
preFilterCap –水平sobel滤波器大小,一般设置31;
SADWindowSize –计算代价的SAD窗口大小:一般设置9;
minDisparity –最小视差,默认为0,此参数决定左图中的像素点在右图匹配搜索的起点。
numberOfDisparity –视察搜索范围,其值必须是16的整数倍,最大搜索边界= numberOfDisparity + minDisparity;
uniquenessRatio –唯一性检测参数,最低代价/次低代价> (1 +uniquenessRatio/100),该像素为有效点;
disp12MaxDiff –左右一致性检测最大容许误差阈值,默认为1;
speckleWindowSize –视差连通区域像素点个数大小,对于每一视差点,当连通区域的像素点个数小于speckleWindowSize时,认为该视差值无效,是噪点;
SpeckleRange –视差连通条件,在计算一个视差点的连通区域时,当下一像素点的视差变化绝对值大于SpeckleRange就认为下一像素点和当前像素点是不连通的。
这里,结合BM开发经历,列出一些自己认为比较重要的参数:
numberOfDisparities -- 最重要,与需要处理场景内容的深度紧密相关,该值越大,处理的深度范围越广,但是视差误匹配会概率性增多;
SADWindowSize -- 一般选取5,7,9,11等,不同取值结果差异大;
uniquenessRatio -- 最低代价与次低代价的比率,该值越大,则该像素点所得视差值越(苛刻)可靠,视差图空洞较多;
speckleWindowSize -- 视差连通区域像素个数的数量,该值越大,面积较小离散斑点被筛除;
speckleRange -- 视差连通条件,当一个像素与相邻下一个像素视差变化超过该值时,则认为它们是不连通的。
关于BM/SGBM算法的使用与参数设置在很多博客中都有讲到,给出一个SGBM参数详细解释的博客地址点击打开链接
关于BM算法解决视差图黑边的代码:
Mat img1p, img2p;
if (STEREO_BM == alg)
{
copyMakeBoarder(img1, img1p, 0, 0, numberOfDisparities, 0, IPL_BORDER_REPLICATE);
copyMakeBoarder(img2, img2p, 0, 0, numberOfDisparities, 0, IPL_BORDER_REPLICATE);
img1 = img1p;
img2 = img2p;
}
if (STEREO_BM == alg)
{
bm->compute(img1, img2, disp);
disp = disp.colRange(numberOfDisparities, img1p.cols);
}
2. Laser Pattern
laser pattern投射到1.5m左右人体表面状态:
HEPTAGON LIMA2.0-SD-0 laser pattern:
HEPTAGON laser datasheet:
3. 空洞修复算法
holefilling算法流程
Input:disp –待修复视差图Output:dstDisp -修复后视差图
Step1.找到disp中未计算深度的空点,空点集合设为Ω;
Step2.遍历每一空点Ω(e),根据其邻域信息δ(e)判断其是否处于空洞中,如果δ(e)内包含一半以上的深度有效像素(validPixel),则认为其为空洞点;
Step3.使用方形滤波器对空洞点进行填补,利益滤波器与有效像素的加权值补充空洞点处深度值,得到dstDisp;
Step4.根据设定的迭代次数(iteration)来,置disp =dstDisp,并重复上述步骤,直至迭代完成,输出结果修复后的dstDisp,并据此生成深度数据。
滤波器及权重设置
采用类似高斯权重设置的方法设置该滤波器权重,离目标像素越远的有效像素,对该空洞点视差值填补的贡献越小。
filterSize滤波器大小选择
滤波器目前可选取5x5, 7x7, 9x9, 11x11.
validPixel有效像素点数选择
例如:使用5x5的滤波器时,需要对空点周边的24个像素值进行深度有效像素点数量的判断,通常认为,空洞点周边应被有效点所环绕,所以此时有效像素点数至少设置为滤波器包含像素一半以上才合理,可设置为validPixel =12;使用其他size滤波器时,有效像素点数设置也应大于滤波器包含像素一半。
iteration迭代次数选择
针对不同的滤波器大小,收敛至较好效果时的迭代次数不一样,需要根据具体场景分析设定。
Source code
void holefilling(Mat _dispSrc, Mat* _dispDst)
{
int64 t = getTickCount();
if (CV_8UC1 != _dispSrc.type())
{
_dispSrc.convertTo(_dispSrc, CV_8UC1);
}
Mat dispBw;
threshold(_dispSrc, dispBw, dispMin, 255, THRESH_BINARY);
dispBw.convetTo(dispBw, CV_32F, 1.0/255);
Mat dispValid;
_dispSrc.convertTo(dispValid, CV_32F);
int margin = filterSize/2;
Mat dispFilt = _dispSrc;
for (int i = margin; i < dispBw.rows; i++)
{
for (int j = margin; j < dispBw.cols; j++)
{
if (0 == dispBw.at(i, j))
{
Mat filtMat = dispBw(Range(i - margin, i + margin + 1), Range(j - margin, j + margin + 1));
Scalar s = sum(filtMat);
if (s[0] > validPixel)
{
Mat tmpWeight;
multiply(filtMat, domainFilter, tmpWeight);
Scalar s1 = sum(tmpWeight);
Mat valid = dispValid(Range(i - margin, i + margin + 1), Range(j - margin, j + margin + 1));
Mat final;
multiply(tmpWeight, valid, final);
Scalar s2 = sum(final);
dispFilt.at(i, j) = (unsigned char)(s2[0] / s1[0]);
}
}
else
{
dispFilt.at(i, j) = (unsigned char)(dispValid.at(i, j));
}
}
}
*dispDst = dispFilt;
t = getTickCount() - t;
printf("Time Elapsed t : %fms\n", t1*1000/getTickFrequency);
}
Source code
static int depthDenoise(Mat _dispSrc, Mat* _dispDenoise)
{
Mat contourBw;
threshold(_dispSrc, contourBw, dispMin, 255, THRESH_BINARY);
vector> contours;
findContours(contourBw, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
double minArea = 10000*scale;
for (int i = contours.size() - 1; i >= 0; i--)
{
double area = countourArea(contours[i]);
if (area < minArea)
{
contours.erase(contours.begin() + i);
}
}
Mat contourDisp(_dispSrc.size(), CV_8UC1, Scalar(0));
drawContours(contourDisp, contours, Scalar(1), -1);
multiply(_dispSrc, contourDisp, *_dispDenoise);
return 0;
}