利用OpenCV打开双目相机并实时处理数据

Dec 3,2018   6043 words   22 min


1.前言

前阵子在淘宝上闲逛,看到有家卖双目摄像头的,价格感觉也还在可接受的范围内。和其它动辄一两千的双目摄像头相比,一两百的价格算是比较良心了。最便宜的6cm基线的100万像素的双目相机才150。 当然如果说差距,肯定还是有的。比如像素、帧率、数据传输速率、影像质量等等,肯定不如贵的。不过自己买来玩玩足够了,这个价格就别要什么自行车了。 所以就在他们家买了个双目摄像头来学习一下,如下图。 正面 处理芯片特写 镜头特写

因为他们家是工厂店,所以双目摄像头的一些参数是按照我的要求生产的。双目摄像头的基线长度是24cm,他们家默认生产的双目相机基线是6cm,最大能生产24cm基线。 因为想着买来是用来做SLAM的,不是小场景的应用,如人脸识别、小车避障这种,所以就要了最大的。镜头选的是100度视场角无畸变的镜头,像素是100万。 按照店家的说法,只支持特定几种分辨率,经过测试也确实如此。这在之后代码部分再详细介绍。 如果感兴趣的话,可以在淘宝里搜“树莓派摄像头模块工厂直销店”就能找到了。

2.双目相机的使用

双目相机的使用非常简单,由于是USB接口的,所以直接插到USB接口即可,免驱。

首先说说双目相机的成像和数据传输形式。双目相机顾名思义就是有两个摄像头,同时拍照。这里的关键词是“同时”,这又属于那种说起来简单做起来难的问题。 如何保证两个相机每一帧都是同时成像的,这会直接关系到双目数据的应用结果。好在厂家已经把“同步”这个问题在硬件层面解决了,通过板载芯片来进行同步。 作为我们用户无需关心这个问题,只需要读取数据即可。

那么双目相机输入给电脑的数据又是什么形式呢?如果不了解,你可能会以为是两个独立的画面或什么的,但其实不是。其实还是“一个画面”,双目相机在硬件层面将两个摄像头的数据合并到了“同一个画面”上,如下所示。 在处理数据时,我们只需要将影像画面拆分成左右两个,如下图,然后单独处理即可。

以我买的这款为例,之前说它只支持几种特定的分辨率,经过实测它支持:2560×960、1280×480、640×240,因此需要在代码中事先设置好。 注意这里所指的分辨率并不是单个相机的分辨率,而是两个相机合并成一张影像后的分辨率,对于单个影像而言,宽度除以2。

下面主要介绍如何通过代码打开双目相机。

3.Python代码
# coding=utf-8
import cv2
import sys


def getCameraInstance(index, resolution):
    """
    用于初始化获取相机实例,从而读取数据

    :param index: 相机的索引编号,如果只有一个相机那就是0,有多个则以此类推
    :param resolution: 相机数据的分辨率设置
    :return: 相机实例,以及设置的影像长宽
    """
    cap = cv2.VideoCapture(index)

    if resolution == '960p':
        width = 2560
        height = 960
    elif resolution == '480p':
        width = 1280
        height = 480
    elif resolution == '240p':
        width = 640
        height = 240

    # OpenCV有相关API可以设置视频流的长宽
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
    return cap, width, height


if __name__ == '__main__':

    # 读取启动参数,设置分辨率
    if sys.argv.__len__() == 3:
        cam_no = int(sys.argv[1])
        reso_flag = sys.argv[2]
    else:
        cam_no = 1
        reso_flag = '960p'

    # 获取相机实例并返回对象
    cap, width, height = getCameraInstance(cam_no, reso_flag)

    # 不断循环读取帧数据
    while True:
        ret, frame = cap.read()

        # 对影像进行拆分,左右影像
        left_cam = frame[:, :int(width / 2), :]
        right_cam = frame[:, int(width / 2):, :]

        # 分别显示
        cv2.imshow("left_cam", left_cam)
        cv2.imshow("right_cam", right_cam)
        cv2.waitKey(25)

运行上述代码后,结果就如下所示。 这样,就能够正常打开双目相机并获取双目相机的数据了。而且由于基于OpenCV,因此在Windows、Ubuntu、树莓派等平台上都是可以运行的。

3.简单应用

下面基于使用双目相机代码的框架,就可以对实时视频流进行一些简单的处理了,例如提取ORB特征点、匹配等。下面分别演示实时ORB提取与匹配。

(1)实时双目ORB特征提取
# coding=utf-8
import cv2


def drawKeyPoints(img, kps, color=(0, 0, 255), rad=3):
    if img.shape.__len__() == 2:
        img_pro = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    else:
        img_pro = img
    for point in kps:
        pt = (int(point.pt[0]), int(point.pt[1]))
        cv2.circle(img_pro, pt, rad, color, 1, cv2.LINE_AA)
    return img_pro


def getORBkpts(img, features=1000):
    orb = cv2.ORB_create(nfeatures=features)
    kp, des = orb.detectAndCompute(img, None)
    return kp, des


def getCameraInstance(index, resolution):
    cap = cv2.VideoCapture(index)

    if resolution == '960p':
        width = 2560
        height = 960
    elif resolution == '480p':
        width = 1280
        height = 480
    elif resolution == '240p':
        width = 640
        height = 240

    cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
    return cap, width, height


if __name__ == '__main__':

    cap, width, height = getCameraInstance(1, '480p')

    while True:
        ret, frame = cap.read()
        left_cam = frame[:, :int(width / 2), :]
        right_cam = frame[:, int(width / 2):, :]

        # ORB特征提取部分
        kps_left, desc_left = getORBkpts(left_cam)
        kps_right, desc_right = getORBkpts(right_cam)
        # 绘制ORB特征点
        left_kps = drawKeyPoints(left_cam, kps_left)
        right_kps = drawKeyPoints(right_cam, kps_right)

        cv2.imshow("left_kps", left_kps)
        cv2.imshow("right_kps", right_kps)
        cv2.waitKey(25)

运行结果如下所示,可以实时检测每帧的ORB特征点。

(2)实时双目ORB特征匹配

在上面实现了实时ORB特征提取,但提取特征点的目的是为了对左右影像进行匹配,从而原理计算视差,再根据双目成像原理计算特征点深度。因此,下面实现ORB特征点匹配。

# coding=utf-8
import cv2
import numpy as np


def drawKeyPoints(img, kps, color=(0, 0, 255), rad=3):
    if img.shape.__len__() == 2:
        img_pro = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    else:
        img_pro = img
    for point in kps:
        pt = (int(point.pt[0]), int(point.pt[1]))
        cv2.circle(img_pro, pt, rad, color, 1, cv2.LINE_AA)
    return img_pro


def drawMatches(img1, img2, good_matches):
    if img1.shape.__len__() == 2:
        img1 = cv2.cvtColor(img1, cv2.COLOR_GRAY2BGR)
    if img2.shape.__len__() == 2:
        img2 = cv2.cvtColor(img2, cv2.COLOR_GRAY2BGR)
    img_out = np.zeros([max(img1.shape[0], img2.shape[0]), img1.shape[1] + img2.shape[1], 3], np.uint8)
    img_out[:img1.shape[0], :img1.shape[1], :] = img1
    img_out[:img2.shape[0], img1.shape[1]:, :] = img2
    for match in good_matches:
        pt1 = (int(match[0]), int(match[1]))
        pt2 = (int(match[2] + img1.shape[1]), int(match[3]))
        cv2.circle(img_out, pt1, 5, (0, 0, 255), 1, cv2.LINE_AA)
        cv2.circle(img_out, pt2, 5, (0, 0, 255), 1, cv2.LINE_AA)
        cv2.line(img_out, pt1, pt2, (0, 0, 255), 1, cv2.LINE_AA)
    return img_out


def getORBkpts(img, features=1000):
    orb = cv2.ORB_create(nfeatures=features)
    kp, des = orb.detectAndCompute(img, None)
    return kp, des


def matchORBkpts(kps1, desp1, kps2, desp2):
    good_kps1 = []
    good_kps2 = []
    good_matches = []

    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

    matches = bf.match(desp1, desp2)

    if matches.__len__() == 0:
        return good_kps1, good_kps2, good_matches
    else:
        min_dis = 10000
        for item in matches:
            dis = item.distance
            if dis < min_dis:
                min_dis = dis

        g_matches = []
        for match in matches:
            if match.distance <= max(1.1 * min_dis, 15.0):
                g_matches.append(match)

        for i in range(g_matches.__len__()):
            good_kps1.append([kps1[g_matches[i].queryIdx].pt[0], kps1[g_matches[i].queryIdx].pt[1]])
            good_kps2.append([kps2[g_matches[i].trainIdx].pt[0], kps2[g_matches[i].trainIdx].pt[1]])
            good_matches.append([kps1[g_matches[i].queryIdx].pt[0], kps1[g_matches[i].queryIdx].pt[1],
                                 kps2[g_matches[i].trainIdx].pt[0], kps2[g_matches[i].trainIdx].pt[1]])

        return good_kps1, good_kps2, good_matches


def getCameraInstance(index, resolution):
    cap = cv2.VideoCapture(index)

    if resolution == '960p':
        width = 2560
        height = 960
    elif resolution == '480p':
        width = 1280
        height = 480
    elif resolution == '240p':
        width = 640
        height = 240

    cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
    return cap, width, height


if __name__ == '__main__':

    cap, width, height = getCameraInstance(1, '480p')

    while True:
        ret, frame = cap.read()
        left_cam = frame[:, :int(width / 2), :]
        right_cam = frame[:, int(width / 2):, :]

        # 获取ORB特征点
        kps_left, desc_left = getORBkpts(left_cam)
        kps_right, desc_right = getORBkpts(right_cam)
        # 左右影像特征点匹配
        good_kps1, good_kps2, good_matches = matchORBkpts(kps_left, desc_left, kps_right, desc_right)
        # 绘制匹配点对
        match_img = drawMatches(left_cam, right_cam, good_matches)
        cv2.imshow("match_img", match_img)
        cv2.waitKey(25)

运行结果如下,可以实时匹配出特征点对。

至此,便简单实现了双目相机的使用以及对其数据的实时处理。最后代码放到了Github上,点击查看

4.小问题

在我的Win10系统上运行上述代码,OpenCV的预览界面的画质非常差,但在Linux下就是正常的,而且在别人的Win10上也是好的。如下对比所示。 初步估计可能是我的Win10缺少相关的视频流的库。因为之前测试在代码中设置视频流格式也不起作用。

本文作者原创,未经许可不得转载,谢谢配合

返回顶部