使用 openCV 和 Python 将对象从一个图像映射到另一个图像

问题描述

这是一个关于使用 openCV(版本 4.5.1.48)和 Python(版本 3.8.5)进行立体声校准和校正的问题。

我在同一轴上放置了两个摄像头,如下图所示:

Sketch

左(上)摄像头以 640x480 分辨率拍摄,而右(下)摄像头以 320x240 分辨率拍摄。 目标是在右侧图像 (320x240) 上找到一个对象,并在左侧图像 (640x480) 上裁剪出相同的对象。换句话说;将构成右侧图像中对象的矩形转移到左侧图像。这个想法概述如下。

Objects

在右侧图像上发现一个红色物体,我需要将其位置转移到左侧图像并将其裁剪掉。物体被放置在距离相机镜头 30 厘米的平面上。换句话说;两个相机镜头到平面的距离(深度)是恒定的(30cm)。

这个主要问题是关于如何将位置从一个图像转移到另一个图像,当两个摄像头并排放置时,当图像具有不同的分辨率以及深度(相当)恒定时。这不是找对象的问题。

解决这个问题,据我所知,必须使用立体校准,我找到了以下文章/代码,其中包括

以下是我使用的校准模式示例:

Gray chessboard pattern

我有 25 张左右摄像头校准图案的照片。图案为 5x9,正方形尺寸为 40x40 毫米。

根据我的知识,我编写了以下代码

import numpy as np
import cv2
import glob

CALIL = "path-to-left-images"
CALIR = "path-to-right-images"

# Termination criterias
criteria1 = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,30,0.001)
criteria2 = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,100,1e-5)

# Chessboard parameters
checker_size = 40.0         # Square size in world units (mm)
checker_pattern = (5,9)    # 5 rows,9 columns

# Flags
findChessboardCorners_flags = 0
#findChessboardCorners_flags |= cv2.CALIB_CB_ADAPTIVE_THRESH
#findChessboardCorners_flags |= cv2.CALIB_CB_norMALIZE_IMAGE
#findChessboardCorners_flags |= cv2.CALIB_CB_FILTER_QUADS
#findChessboardCorners_flags |= cv2.CALIB_CB_FAST_CHECK

calibrateCamera_flags = 0
#calibrateCamera_flags |= cv2.CALIB_USE_INTRINSIC_GUESS
#calibrateCamera_flags |= cv2.CALIB_FIX_PRINCIPAL_POINT
#calibrateCamera_flags |= cv2.CALIB_FIX_ASPECT_RATIO
#calibrateCamera_flags |= cv2.CALIB_ZERO_TANGENT_disT
#calibrateCamera_flags |= cv2.CALIB_FIX_K1 # K2,K3...K6
#calibrateCamera_flags |= cv2.CALIB_RATIONAL_MODEL
#calibrateCamera_flags |= cv2.CALIB_THIN_PRISM_MODEL
#calibrateCamera_flags |= cv2.CALIB_FIX_S1_S2_S3_S4
#calibrateCamera_flags |= cv2.CALIB_TILTED_MODEL
#calibrateCamera_flags |= cv2.CALIB_FIX_TAUX_TAUY

stereoCalibrate_falgs = 0
stereoCalibrate_falgs |= cv2.CALIB_FIX_INTRINSIC
#stereoCalibrate_falgs |= cv2.CALIB_USE_INTRINSIC_GUESS
#stereoCalibrate_falgs |= cv2.CALIB_USE_EXTRINSIC_GUESS
#stereoCalibrate_falgs |= cv2.CALIB_FIX_PRINCIPAL_POINT
#stereoCalibrate_falgs |= cv2.CALIB_FIX_FOCAL_LENGTH
#stereoCalibrate_falgs |= cv2.CALIB_FIX_ASPECT_RATIO
#stereoCalibrate_falgs |= cv2.CALIB_SAME_FOCAL_LENGTH
#stereoCalibrate_falgs |= cv2.CALIB_ZERO_TANGENT_disT
#stereoCalibrate_falgs |= cv2.CALIB_FIX_K1 # K2,K3...K6
#stereoCalibrate_falgs |= cv2.CALIB_RATIONAL_MODEL
#stereoCalibrate_falgs |= cv2.CALIB_THIN_PRISM_MODEL
#stereoCalibrate_falgs |= cv2.CALIB_FIX_S1_S2_S3_S4
#stereoCalibrate_falgs |= cv2.CALIB_TILTED_MODEL
#stereoCalibrate_falgs |= cv2.CALIB_FIX_TAUX_TAUY

stereoRectify_flags = 0
stereoRectify_flags |= cv2.CALIB_ZERO_disPARITY

# Prepare object points,like (0,0),(1,(2,0) ....,(6,5,0)
objp = np.zeros((1,checker_pattern[0] * checker_pattern[1],3),np.float32)
objp[0,:,:2] = np.mgrid[0:checker_pattern[0],0:checker_pattern[1]].T.reshape(-1,2)*checker_size

# Arrays to store object points and image points from all the images.
objPoints = []      # 3d point in real world space
imgPointsL = []     # 2d points in image plane,left image (normal)
imgPointsR = []     # 2d points in image plane,right image (thermal)

# Get calibration images
# Get all left (normal) images from directory. Sort them
images_left = glob.glob(CALIL+'*')
images_left.sort()
# Get all right (thermal) images from directory. Sort them
images_right = glob.glob(CALIR+'*')
images_right.sort()

for left_img,right_img in zip(images_left,images_right):
    # Left object points
    imgL = cv2.imread(left_img)
    grayL = cv2.cvtColor(imgL,cv2.COLOR_BGR2GRAY)

    # Find the chessboard corners
    retL,cornersL = cv2.findChessboardCorners(
        grayL,(checker_pattern[0],checker_pattern[1]),findChessboardCorners_flags)

    # Right object points
    imgR = cv2.imread(right_img)
    grayR = cv2.cvtColor(imgR,cv2.COLOR_BGR2GRAY)

    # Find the chessboard corners
    retR,cornersR = cv2.findChessboardCorners(
        grayR,findChessboardCorners_flags)

    if retL and retR:
        # If found,add object points,image points (after refining them)
        objPoints.append(objp)
        
        # Left points
        cornersL2 = cv2.cornerSubPix(
            grayL,cornersL,(5,5),(-1,-1),criteria1)
        imgPointsL.append(cornersL2)
        
        # Right points
        cornersR2 = cv2.cornerSubPix(
            grayR,cornersR,criteria1)
        imgPointsR.append(cornersR2)

shapeL = grayL.shape[::-1]
shapeR = grayR.shape[::-1]

# Calibrate each camera separately
retL,K1,D1,R1,T1 = cv2.calibrateCamera(
    objPoints,imgPointsL,shapeL,None,flags=calibrateCamera_flags)
retR,K2,D2,R2,T2 = cv2.calibrateCamera(
    objPoints,imgPointsR,shapeR,flags=calibrateCamera_flags)

# Stereo calibrate
ret,R,T,E,F = cv2.stereoCalibrate(
    objPoints,flags=calibrateCamera_flags,criteria=criteria2)

# Stereo rectify
R1,P1,P2,Q,roi_left,roi_right = cv2.stereoRectify(
    K1,flags=stereoRectify_flags,alpha=1)

# Undistort images
leftMapX,leftMapY = cv2.initUndistortRectifyMap(
    K1,cv2.CV_32FC1)
rightMapX,rightMapY = cv2.initUndistortRectifyMap(
    K2,cv2.CV_32FC1)

# Remap
left_rectified = cv2.remap(images_left[0],leftMapX,leftMapY,cv2.INTER_LINEAR,cv2.BORDER_CONSTANT)
right_rectified = cv2.remap(images_right[0],rightMapX,rightMapY,cv2.BORDER_CONSTANT)

但我得到了一个糟糕的结果:

Result

我尝试了不同的标志、alpha 参数,但没有任何效果...

问题:

  • 当两幅图像的分辨率不同时,是否可以进行立体校准并解决这个问题?
  • 一般工作流程是正确的还是我遗漏了什么?旗帜?阿尔法参数?解决此问题的其他方法是什么?

编辑

在 Micha 的精彩评论之后,我发现透视单应性(希望)是解决这个问题的方法,而不是立体校准。这是因为需要找到的物体被放置在距离两个相机镜头(30 厘米)等长/深度的平面上。

根据新信息,我编写了以下代码,其中我使用了第一对图像来获取透视变换矩阵:

imgL = cv2.imread(images_left[0])
imgL = cv2.cvtColor(imgL,cv2.COLOR_BGR2GRAY)
imgR = cv2.imread(images_right[0])
imgR = cv2.cvtColor(imgR,cv2.COLOR_BGR2GRAY)

ret1,corners1 = cv2.findChessboardCorners(imgL,checker_pattern[1]))
cornersL2 = cv2.cornerSubPix(imgL,corners1,criteria1)

ret2,corners2 = cv2.findChessboardCorners(imgR,checker_pattern[1]))
cornersR2 = cv2.cornerSubPix(imgR,corners2,criteria1)

H,_ = cv2.findHomography(cornersL2,cornersR2)

基于透视变换矩阵 H,我可以使用 cv2.warpPerspective() 函数根据右图像和校准板中的棋盘角来扭曲左图像。

但是,当我尝试扭曲它时,扭曲的图像(下图)相对于另一个(下图)图像有点偏右,如下图所示:

Warp image test

裁剪的结果如下图所示,其中区域不匹配:

Warp result

我想我需要调整扭曲图像的大小,使其与正确的图像 (320x240) 具有相同的分辨率。扭曲图像的分辨率为 640x240。

问题:

  • 校准板是否应该放置在距相机镜头 30 厘米处,以便对透视变换矩阵进行最佳计算?
  • 我有 25 张来自不同角度的校准板图像。是否需要使用所有图片,还是只使用一张图片
  • 我正在使用 cv2.warpPerspective() 函数,但裁剪不匹配。我应该使用其他功能吗?

解决方法

我通过使用以下 openCV 函数解决了这个问题:

  • cv2.findChessboardCorners()
  • cv2.cornerSubPix()
  • cv2.findHomography()
  • cv2.warpPerspective()

我使用距离为 30cm 的校准板来计算透视变换矩阵 H。因此,我可以将对象从右侧图像映射到左侧图像。虽然深度必须是恒定的(30 厘米),这有点问题,但在我的情况下是可以接受的。

感谢@Micka 的精彩回答。