立体相机如何创建好的深度图?

Posted

技术标签:

【中文标题】立体相机如何创建好的深度图?【英文标题】:How a good depth map can be created with stereo cameras? 【发布时间】:2021-03-29 03:22:53 【问题描述】:

我正在研究一个项目很长时间。我的目标是从立体相机的图像中获取深度图并过滤人类,以便计算里面的人。

我正在尝试校准我的相机,持续 1-2 个月。然而,当我在整流对上绘制极线时,结果不够好(我附上了我的整流对结果)。我现在正在工作,我的校准结果平均不错,并试图从视差图中获取深度图。我已经录制了一个图像序列、.avi 文件,当我尝试从这个视频中获取深度图时,当我尝试这个时,我面临着一个不稳定的情况。前一帧中为白色的点在下一帧中可能非常黑。所以我不能只通过过滤差异来计算人数。我使用 SGBM 从校正后的图像中获取深度。在这个项目中我仍然被认为是业余爱好者。我愿意接受任何建议。 (如何做更好的校准?更好的视差图?更好的深度图?)

这是深度图和校正对:

整流对和极线

我几乎用 600 对校准了我的相机并对其进行了改进。我的总体平均误差是 0.13 像素,包含 35 对图像。

minDisparity=-1,
        numDisparities=2*16,  # max_disp has to be dividable by 16 f. E. HH 192, 256
        blockSize=window_size,
        P1=8 * 3 * window_size,
        # wsize default 3; 5; 7 for SGBM reduced size image; 15 for SGBM full size image (1300px and above); 5 Works nicely
        P2=32 * 3 * window_size,
        disp12MaxDiff=12,
        uniquenessRatio=1,
        speckleWindowSize=50,
        speckleRange=32,
        preFilterCap=63,
        mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY

这是我的块匹配参数。

【问题讨论】:

“我仍然被认为是这个项目的业余爱好者。”业余爱好者是不因做某事而获得报酬的人,他们这样做是为了好玩。与确实获得报酬的专业人士相反,他们这样做是为了工作。业余爱好者不擅长做某事或知道的比专业人士少的概念是愚蠢的。我发现业余爱好者通常比大多数专业人士知识渊博。也许您的意思是说您是新手(经验不足)? 感谢您的评论。我会考虑的。 :) 【参考方案1】:

几件事情一目了然:

您在 StereoSGBM 中的 P1、P2 参数应该是平方的,计算如下:

P1 = 8*3*blockSize**2
P2 = 32*3*blockSize**2

StereoSGBM 支持彩色图像,请尝试跳过灰度转换。如果使用灰度,则应去掉 P1、P2 参数中的 *3 乘数。这是针对图像通道的数量,其中灰度为 1。

您正在使用cv2.STEREO_SGBM_MODE_3WAY,它更快但不太准确。要获得更好但更慢的结果,请尝试使用cv2.STEREO_SGBM_MODE_SGBM(默认,5 个邻居)或cv2.STEREO_SGBM_MODE_HH(8 个邻居)

您的图像具有不同的曝光度,如果可能,请尝试修复相机的 AWB/增益,以便始终如一地捕获它们。

【讨论】:

【参考方案2】:

为什么要使用距离图来检测人类?在我看来,这是一个对象检测问题。

无论如何,在当前获取距离图的技术状态下,我会推荐基于人工智能的模型。

像 NeRF 这样的模型已经取得了惊人的成果。

Google 提供 ARCore,它提供深度图,但基于单个摄像头。 英伟达有this project This,基于视频中的Nerf实现3D重建

几周后我将致力于此,我想实现一个在 TensorFlowLite 中运行的模型,该模型从立体相机实现深度图

【讨论】:

【参考方案3】:

为了改善视差图的结果,您可以实现后过滤,这里有一个教程(https://docs.opencv.org/master/d3/d14/tutorial_ximgproc_disparity_filtering.html)。我还使用了一个额外的散斑过滤器和填充缺失差异的选项。 python实现如下:

stereoProcessor = cv2.StereoSGBM_create(
                minDisparity=0,
                numDisparities = max_disparity, # max_disp has to be dividable by 16 f. E. HH 192, 256
                blockSize=window_size,
                P1 = p1,       # 8*number_of_image_channels*SADWindowSize*SADWindowSize
                P2 = p2,    # 32*number_of_image_channels*SADWindowSize*SADWindowSize
                disp12MaxDiff=disp12Maxdiff,
                uniquenessRatio= uniquenessRatio,
                speckleWindowSize=speckle_window,
                speckleRange=speckle_range,
                preFilterCap=prefiltercap,
               # mode=cv2.STEREO_SGBM_MODE_HH# numDisparities = max_disparity, # max_disp has to be dividable by 16 f. E. HH 192, 256
                
        )
        
        #stereoProcessor = cv2.StereoBM_create(numDisparities=16, blockSize=15)
        
        # set up left to right + right to left left->right + right->left matching +
        # weighted least squares filtering (not used by default)

        left_matcher = stereoProcessor
        right_matcher = cv2.ximgproc.createRightMatcher(left_matcher)

        #Image information 
        height, width, channels = I.shape

        frameL= I[:,0:int(width/2),:]
        frameR = I[:,int(width/2):width,:]

        # remember to convert to grayscale (as the disparity matching works on grayscale)

        grayL = cv2.cvtColor(frameL,cv2.COLOR_BGR2GRAY)
        grayR = cv2.cvtColor(frameR,cv2.COLOR_BGR2GRAY)

        # perform preprocessing - raise to the power, as this subjectively appears
        # to improve subsequent disparity calculation

        grayL = np.power(grayL, 0.75).astype('uint8')
        grayR = np.power(grayR, 0.75).astype('uint8')

        # compute disparity image from undistorted and rectified versions
        # (which for reasons best known to the OpenCV developers is returned scaled by 16)

        if (wls_filter):

            wls_filter = cv2.ximgproc.createDisparityWLSFilter(matcher_left=left_matcher)
            wls_filter.setLambda(wls_lambda)
            wls_filter.setSigmaColor(wls_sigma)
            displ = left_matcher.compute(cv2.UMat(grayL),cv2.UMat(grayR))  # .astype(np.float32)/16
            dispr = right_matcher.compute(cv2.UMat(grayR),cv2.UMat(grayL))  # .astype(np.float32)/16
            displ = np.int16(cv2.UMat.get(displ))
            dispr = np.int16(cv2.UMat.get(dispr))
            disparity = wls_filter.filter(displ, grayL, None, dispr)
        else:

            disparity_UMat = stereoProcessor.compute(cv2.UMat(grayL),cv2.UMat(grayR))
            disparity = cv2.UMat.get(disparity_UMat)
        
        speckleSize = math.floor((width * height) * 0.0005)
        maxSpeckleDiff = (8 * 16) # 128

        cv2.filterSpeckles(disparity, 0, speckleSize, maxSpeckleDiff)
        
        # scale the disparity to 8-bit for viewing
        # divide by 16 and convert to 8-bit image (then range of values should
        # be 0 -> max_disparity) but in fact is (-1 -> max_disparity - 1)
        # so we fix this also using a initial threshold between 0 and max_disparity
        # as disparity=-1 means no disparity available

        _, disparity = cv2.threshold(disparity,0, max_disparity * 16, cv2.THRESH_TOZERO)
        disparity_scaled = (disparity / 16.).astype(np.uint8)

        # fill disparity if requested

        if (fill_missing_disparity):

            _, mask = cv2.threshold(disparity_scaled,0, 1, cv2.THRESH_BINARY_INV)
            mask[:,0:120] = 0
            disparity_scaled = cv2.inpaint(disparity_scaled, mask, 2, cv2.INPAINT_NS)

        # display disparity - which ** for display purposes only ** we re-scale to 0 ->255
        disparity_to_display = (disparity_scaled * (256. / self.value_NumDisp)).astype(np.uint8)
        

【讨论】:

以上是关于立体相机如何创建好的深度图?的主要内容,如果未能解决你的问题,请参考以下文章

来自形成立体系统的两个校准相机的 openCV 深度图

使用立体相机从视差图进行深度重建

深度相机-介绍

立体视觉:深度估计

如何使用立体对合成新的相机视图?

OpenCV中的立体图像创建深度图