理想状态下,双目相机拍出的两张照片left和right种,left图像中的某一个像素应当可以在right中同一行(opencv中的row)中找到该像素对应的像素,根据这两个像素的x坐标差,便可以得到图像的深度图。但是并非每一个像素都可以找到其对应像素,原因有:
(1)由于遮挡关系,left中的像素无法在right中找到
(2)由于某些处于非朗伯面(比如水面、镜面、玻璃)的点由于光照原因无法匹配
(3)密集重复纹理(树林、草地)下或纹理特别稀疏(墙面、天空)时,很难准确匹配
(4)纹理都是水平线条时(线条与极线平行),很难准确匹配
此处,鄙人给出最简单的双目匹配的实现方式,即采用块匹配的方式计算深度图。
#pragma once
#include
#include
#include"myexception.h"
using std::vector;
class stereo {
public:
stereo(const cv::Mat& _left, const cv::Mat& _right);
~stereo() {}
cv::Mat getDepthImage(const int half_window_size = 4, const int threshold = 20);
void getGradient(void);
inline int SAD(const int left_row, const int left_col, const int right_row, const int right_col, int half_window_size);
protected:
cv::Mat getMergeImage();
cv::Mat left;
cv::Mat right;
cv::Mat gleft;
cv::Mat gright;
};
#include "stereo.h"
stereo::stereo(const cv::Mat& _left, const cv::Mat& _right): left(_left), right(_right){
gleft = cv::Mat(left.rows, left.cols, CV_16SC1, cv::Scalar(0));
gright = cv::Mat(right.rows, right.cols, CV_16SC1, cv::Scalar(0));
}
void stereo::getGradient(void) {
int row = left.rows;
int col = left.cols;
for (int i = 0; i < row; ++i) {
for (int j = 1; j < col - 1; ++j) {
gleft.at(i, j) = abs(short(left.at(i, j - 1)) - left.at(i, j + 1));
gright.at(i, j) = abs(short(right.at(i, j - 1)) - right.at(i, j + 1));
}
}
}
cv::Mat stereo::getMergeImage() {
int row = gleft.rows;
int col = gleft.cols;
cv::Mat merge(left.rows, left.cols, CV_8UC1);
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
int temp = 0;
temp = abs(gleft.at(i, j)) + abs(gright.at(i, j));
temp >>= 1;
if (temp > 255)temp = 255;
merge.at(i, j) = temp;
}
}
return merge;
}
cv::Mat stereo::getDepthImage(const int half_window_size, const int threshold) {
if (half_window_size < 1)throw param_exception("the param half_window_size should upper than 1");
if(threshold < 0)throw param_exception("the param threshold should upper than 0");
int row = left.rows;
int col = left.cols;
cv::Mat depth(row, col, CV_8UC3, cv::Scalar(0, 0, 0));
int window_size = (half_window_size << 1) + 1;
int search_range = col * 0.2;
for (int i = half_window_size; i < row - half_window_size; ++i) {
for (int j = half_window_size; j < col - half_window_size; ++j) {
uchar* pleft = &(left.data[i * col + j]);
if (gleft.at(i, j) < threshold)continue;
int search_left = j - search_range;
if (search_left < half_window_size) search_left = half_window_size;
int search_right = j + search_range;
if (search_right > col - half_window_size) search_right = col - half_window_size;
int min_ssd = 0x0fffffff;
int min_k = search_left;
for (int k = search_left; k < search_right; ++k) {
int ssd = SAD(i, j, i, k, half_window_size);
if (ssd < min_ssd) {
min_ssd = ssd;
min_k = k;
}
}
if(min_ssd < window_size * window_size * 9){
int tmp = abs(min_k - j);
if (tmp == 0)
tmp = 0xffff;
else
tmp = (481) / tmp;
depth.at(i, j) = cv::Vec3b(tmp >> 8, tmp & 0xff, tmp & 0xff);
}
else {
depth.at(i, j) = cv::Vec3b(0, 0, 0);
}
}
}
return depth;
}
inline int stereo::SAD(const int left_row, const int left_col, const int right_row, const int right_col, int half_window_size) {
int window_size = 1 + (half_window_size << 1);
int sum = 0;
for (int i = -half_window_size; i <= half_window_size; ++i) {
for (int j = -half_window_size; j <= half_window_size; ++j) {
int temp = int(left.at(left_row + i, left_col + j)) - right.at(right_row + i, right_col + j);
sum += abs(temp);
}
}
return sum;
}
注:half_window_size是窗口的半径,越大,匹配越准确,但是速度越慢,threshold为阈值,即将梯度小的点去除掉,可以加快计算并且忽略掉纹理变换小的区域,可以设置为0计算所有像素
函数的使用方式为:
cv::Mat img1 = cv::imread("G:\\dataset\\sequences\\00\\image_0\\000000.png", CV_LOAD_IMAGE_GRAYSCALE);
cv::Mat img2 = cv::imread("G:\\dataset\\sequences\\00\\image_1\\000000.png", CV_LOAD_IMAGE_GRAYSCALE);
stereo st(img1, img2);
st.getGradient();
auto depth = st.getDepthImage();
for (float i = 1; i < 10; i += 0.2) {
cv::imshow("depth", depth * i);
cv::waitKey();
}//调节亮度