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缺少相关的视频流的库。因为之前测试在代码中设置视频流格式也不起作用。
本文作者原创,未经许可不得转载,谢谢配合