基于 MiDaS和Python 开始做深度估计

点击上方“小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

测量物体与相机之间的距离在计算机视觉领域中面临着重大挑战,原因包括2D图像中缺乏固有深度信息、透视失真、物体尺寸变化、相机校准要求以及在复杂场景中的遮挡。例如,通过透视投影进行的距离估计依赖于传感器尺寸、焦距和物体的实际高度等变量。这些未知变量的计算增加了任务的复杂性。

86a7a15251c80ceb6af828f134a3f11c.jpeg

物体距离的公式

一系列基于传统方法和深度学习的方法已经在一段时间内提供了有效的距离估计解决方案。涉及立体视觉的解决方案已被证明在深度计算中是有效而准确的,然而始终在寻找更高效、更经济的替代方案。深度学习在这些限制中显示出色,将可能性的边界推向一个全新的水平,并实现了单目视觉深度估计模型的现实应用,本文将探讨其中的一种方法。

在本文中,将使用MediaPipe姿势估计模块和MiDaS深度估计模型的混合方法来估计物体的距离。但在此之前,让我们快速概述一下本文将涵盖的内容:

  • MiDaS概述。

  • 使用MediaPipe关键点和MiDaS深度图进行距离测量。

MiDaS

0a0069b92010906aea3e792d2137f1e0.jpeg

MiDaS深度估计模型

MiDaS(Multiple Depth Estimation Accuracy with Single Network)是基于深度学习的残差模型,建立在Res-Net之上,用于单目深度估计。MiDaS已被证明在从单个图像中进行深度估计方面取得了有希望的结果。以下是MiDaS架构的通用概述:

1. 编码器-解码器结构

MiDaS基于编码器-解码器结构,其中编码器部分负责高级特征提取,解码器通过上采样从这些特征生成深度图。

2. 骨干网络

MiDaS通常使用残差网络(ResNet-50或ResNet-101)进行特征提取,因为它对于消失梯度是稳健的。这允许MiDaS从输入图像中提取多通道特征图,捕捉不同尺度上的分层信息。

3. 多尺度特征融合:

在MiDaS中,使用跳跃连接和特征融合来实现精确的深度估计。通过跳跃连接将早期层的特征图连接到后期层,以在上采样过程中访问低级别的细节。通过特征融合,多尺度特征图结合在一起,以确保对深度估计的局部和全局信息的有效利用。

4. 上采样和细化:

   使用上采样生成最终深度图。常用的上采样技术包括双线性插值或转置卷积,以增加特征图的空间分辨率。通过特征融合,将深度图与相应的跳跃连接结合起来,以细化深度估计。

以下是用于距离测量的Python代码:

import cv2
import torch
import mediapipe as mp
import numpy as np
from scipy.interpolate import RectBivariateSpline
mp_pose = mp.solutions.pose
pose = mp_pose.Pose(static_image_mode=False)

导入所需的包并初始化Media-pipe姿势估计类`mp_pose.Pose`。

#Downloading the model from TorchHub.
midas = torch.hub.load('intel-isl/MiDaS','MiDaS_small')
midas.to('cpu')
midas.eval()

从torch hub下载MiDaS_small模型。您可以通过GitHub一次性下载MiDaS的Python可执行文件。torch hub上有三个MiDaS的变体,可以通过用‘DPT_Large’或‘DPT_Hybrid’替换‘MiDaS_small’来下载。所有三个变体的一般性能如下:

  1. 小型变体:准确性最低,推断速率最高。

  2. 混合变体:中等准确性和中等推断速度。

  3. 大型变体:准确性最高,推断速度最低。

如果您有兼容Cuda的GPU,则可以将`midas.to('cpu')`替换为`midas.to("cuda")`以最大化推断速度。

#Performing preprocessing on input for small model 
transforms = torch.hub.load('intel-isl/MiDaS','transforms')
transform = transforms.small_transform
#Converting Depth to distance
def depth_to_distance(depth_value,depth_scale):
  return -1.0/(depth_value*depth_scale)

对MiDaS小模型的输入图像/视频帧进行所需的预处理。接下来,定义了一个名为`depth_to_distance`的函数,用于将计算得到的深度值转换为相应的距离值。

cap = cv2.VideoCapture('')
while cap.isOpened():
  ret, frame = cap.read()


  img = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)


  cv2.imshow('Walking',img)


  if cv2.waitKey(2) &0xFF == ord('q'):
    cap.release()
    cv2.destroyAllWindows()

读取视频输入并使用`cv2.cvtColor`函数进行颜色空间转换。由于cv2以BGR格式读取图像,我们需要将其转换为RGB以进行标准的可视化。让我们运行代码,检查到目前为止是否正常工作。

b3d92aa068dd472f24938a3b2f1de051.png

接下来,我们将使用下面的代码从视频帧中提取关键点(landmarks)使用 MediaPipe。

# Detect the body landmarks in the frame
    results = pose.process(img)


    # Check if landmarks are detected
    if results.pose_landmarks is not None:
        # Draw Landmarks
        mp_drawing = mp.solutions.drawing_utils
        mp_drawing.draw_landmarks(img, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)

b0691ab80317fc71c6fdd9e61f1813a5.png

在这段代码中,首先加载了MiDaS模型和MediaPipe的姿势估计模块,然后对输入图像进行了一系列的预处理。接下来,通过检测姿势关键点,可以在图像上绘制出检测到的关键点。

Extract Landmark Coordinates        landmarks = []
        for landmark in results.pose_landmarks.landmark:
            landmarks.append((landmark.x, landmark.y, landmark.z))
        # Extract left and right waist Landmarks
        waist_landmarks = [results.pose_landmarks.landmark[mp_pose.PoseLandmark.LEFT_HIP],
                           results.pose_landmarks.landmark[mp_pose.PoseLandmark.RIGHT_HIP]]
        #Finding midpoint from waist    
        mid_point = ((waist_landmarks[0].x + waist_landmarks[1].x) / 2, (waist_landmarks[0].y + waist_landmarks[1].y) / 2)
        mid_x , mid_y = mid_point

提取两个关键点的x和y坐标值并计算中点。根据使用情况,可以从Media-pipe的姿势关键点列表中选择任意关键点。接下来,我们将通过MiDaS深度估计模型传递视频以获取深度图。

imgbatch = transform(img).to('cpu')


    # Making a prediction
    with torch.no_grad():
        prediction = midas(imgbatch)
        prediction = torch.nn.functional.interpolate(
            prediction.unsqueeze(1),
            size=img.shape[:2],
            mode='bicubic',
            align_corners=False
        ).squeeze()


    output = prediction.cpu().numpy()
    #Normalizing the output predictions for cv2 to read.
    output_norm = cv2.normalize(output, None, 0, 1, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32F)


    cv2.imshow('Walking',output_norm)

f61de7756f9c2552961a11565ff1f8c2.png

上图是通过MiDaS提取的深度图,也可以将`waitKey`的值更改为1以减少帧延迟。您还可以使用下面的代码将深度图的输出更改为彩色图,但在本项目中我们将使用标准的黑白图。

#Colored Depth map
output_norm = (output_norm*255).astype(np.uint8)
output_norm = cv2.applyColorMap(output_norm, cv2.COLORMAP_MAGMA)

然后,我们将使用先前提取的腰部关键点来使用MiDaS计算深度值。

#Creating a spline array of non-integer grid
        h , w = output_norm.shape
        x_grid = np.arange(w)
        y_grid = np.arange(h)


        # Create a spline object using the output_norm array
        spline = RectBivariateSpline(y_grid, x_grid, output_norm)

以上代码片段中使用的样条数组的目的是在非整数网格上创建输出规范数组的平滑连续表示。对样条数组的需求源于对输出预测进行归一化,从而产生一个包含浮点值的数组。通过利用样条对象,可以根据给定的数据插值计算更准确和更灵活的结果或可视化。

#Passing the x and y co-ordinates distance function to calculate distance.
#Tweak with the depth scale to see what suits you!
depth_scale = 1
depth_mid_filt = spline(mid_y,mid_x)
depth_midas = depth_to_distance(depth_mid_filt, depth_scale)


#Displaying the distance.
cv2.putText(img, "Depth in unit: " + str(
            np.format_float_positional(depth_mid_filt , precision=3)), (20, 50), cv2.FONT_HERSHEY_SIMPLEX,
                    1, (255, 255, 255), 3)

cee5c346d233f6eb1299b7b528c771a5.gif

深度值有些波动。为了稳定数值,我们将在深度值上使用指数均值滤波器,然后查看改进。

13d446ca5f34c26b282b4cd005639e94.gif

在应用指数均值滤波器后,我们可以看到深度值的波动明显减小。让我们看一下整个代码片段:

import cv2
import torch
import matplotlib.pyplot as plt
import mediapipe as mp
import numpy as np
import shutil
from scipy.interpolate import RectBivariateSpline


#To Clear the model cache
# shutil.rmtree(torch.hub.get_dir(), ignore_errors=True)


#Initializing the body landmarks detection module
mp_pose = mp.solutions.pose
pose = mp_pose.Pose(static_image_mode=False)


# #download the model
midas = torch.hub.load('intel-isl/MiDaS','MiDaS_small')
midas.to('cpu')
midas.eval()


#Process image
transforms = torch.hub.load('intel-isl/MiDaS','transforms')
transform = transforms.small_transform


alpha = 0.2
previous_depth = 0.0
depth_scale = 1.0


#Applying exponential moving average filter
def apply_ema_filter(current_depth):
    global previous_depth
    filtered_depth = alpha * current_depth + (1 - alpha) * previous_depth
    previous_depth = filtered_depth  # Update the previous depth value
    return filtered_depth




#Define depth to distance
def depth_to_distance(depth_value,depth_scale):
    return 1.0 / (depth_value*depth_scale)


def depth_to_distance1(depth_value,depth_scale):
    return -1.0 / (depth_value*depth_scale)


cap = cv2.VideoCapture('distance1.mp4')
while cap.isOpened():
    ret, frame = cap.read()


    img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)


    # Detect the body landmarks in the frame
    results = pose.process(img)


    # Check if landmarks are detected
    if results.pose_landmarks is not None:
        # Draw Landmarks
        # mp_drawing = mp.solutions.drawing_utils
        # mp_drawing.draw_landmarks(img, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)


        # Extract Landmark Coordinates
        landmarks = []
        for landmark in results.pose_landmarks.landmark:
            landmarks.append((landmark.x, landmark.y, landmark.z))


        waist_landmarks = [results.pose_landmarks.landmark[mp_pose.PoseLandmark.LEFT_HIP],
                           results.pose_landmarks.landmark[mp_pose.PoseLandmark.RIGHT_HIP]]


        mid_point = ((waist_landmarks[0].x + waist_landmarks[1].x) / 2, (waist_landmarks[0].y + waist_landmarks[1].y) / 2,(waist_landmarks[0].z + waist_landmarks[1].z) /2)
        mid_x,mid_y = mid_point


        
        imgbatch = transform(img).to('cpu')


        # Making a prediction
        with torch.no_grad():
            prediction = midas(imgbatch)
            prediction = torch.nn.functional.interpolate(
                prediction.unsqueeze(1),
                size=img.shape[:2],
                mode='bicubic',
                align_corners=False
            ).squeeze()


        output = prediction.cpu().numpy()
        output_norm = cv2.normalize(output, None, 0, 1, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32F)


        # Creating a spline array of non-integer grid
        h, w = output_norm.shape
        x_grid = np.arange(w)
        y_grid = np.arange(h)


        # Create a spline object using the output_norm array
        spline = RectBivariateSpline(y_grid, x_grid, output_norm)
        depth_mid_filt = spline(mid_y,mid_x)
        depth_midas = depth_to_distance(depth_mid_filt, depth_scale)
        depth_mid_filt = (apply_ema_filter(depth_midas)/10)[0][0]
        
        cv2.putText(img, "Depth in unit: " + str(
            np.format_float_positional(depth_mid_filt , precision=3)) , (20, 50), cv2.FONT_HERSHEY_SIMPLEX,
                    1, (255, 255, 255), 3)
        
        cv2.imshow('Walking',img)


    if cv2.waitKey(1) &0xFF == ord('q'):
        cap.release()
        cv2.destroyAllWindows()

结论

在本文中,我们利用MiDaS深度估计模型计算了物体与相机之间的距离,同时利用了从Media-pipe提取的参考关键点。这种方法可以用于检测多个物体/人的距离,并集成到基于接近性的小型项目中。

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。


下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。


下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。


交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值