【OpenCV】双相机结构光成像与图像交叉融合实现【python篇】

双相机结构光成像与图像交叉融合实现

下面我将详细介绍如何使用Python实现双相机结构光成像系统及其图像交叉融合技术。这个系统通常用于三维重建、工业检测等领域。

系统架构概述

双相机结构光系统通常包含以下组件:

  1. 两个同步的工业相机
  2. 结构光投影仪(DLP或LCD)
  3. 计算机处理系统
  4. 标定装置

实现步骤

1. 硬件设置与相机同步

python 复制代码
import cv2
import numpy as np
import time

class DualCameraSystem:
    def __init__(self, cam1_index=0, cam2_index=1):
        """初始化双相机系统"""
        self.cam1 = cv2.VideoCapture(cam1_index)
        self.cam2 = cv2.VideoCapture(cam2_index)
        
        # 设置相机参数
        self._setup_cameras()
        
    def _setup_cameras(self):
        """配置相机参数"""
        # 设置分辨率
        self.cam1.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
        self.cam1.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
        self.cam2.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
        self.cam2.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
        
        # 设置帧率
        self.cam1.set(cv2.CAP_PROP_FPS, 30)
        self.cam2.set(cv2.CAP_PROP_FPS, 30)
        
        # 设置曝光时间(根据实际相机支持)
        self.cam1.set(cv2.CAP_PROP_EXPOSURE, -4)  # 示例值
        self.cam2.set(cv2.CAP_PROP_EXPOSURE, -4)
    
    def capture_synchronized(self):
        """同步捕获图像"""
        # 实际项目中可能需要硬件触发实现精确同步
        ret1, frame1 = self.cam1.read()
        ret2, frame2 = self.cam2.read()
        
        if ret1 and ret2:
            return frame1, frame2
        else:
            raise RuntimeError("相机捕获失败")
    
    def release(self):
        """释放相机资源"""
        self.cam1.release()
        self.cam2.release()

2. 结构光图案生成

python 复制代码
class StructuredLightPatterns:
    @staticmethod
    def generate_gray_code_patterns(width, height, bits=8):
        """生成格雷码图案序列"""
        patterns = []
        
        # 水平方向格雷码
        for i in range(bits):
            pattern = np.zeros((height, width), dtype=np.uint8)
            period = 2 ** (bits - i)
            value = 0
            
            for x in range(width):
                if x % (period // 2) == 0:
                    value = 255 - value
                pattern[:, x] = value
            
            patterns.append(pattern)
        
        # 垂直方向格雷码(如果需要)
        for i in range(bits):
            pattern = np.zeros((height, width), dtype=np.uint8)
            period = 2 ** (bits - i)
            value = 0
            
            for y in range(height):
                if y % (period // 2) == 0:
                    value = 255 - value
                pattern[y, :] = value
            
            patterns.append(pattern)
        
        return patterns
    
    @staticmethod
    def generate_sinusoidal_patterns(width, height, frequencies=[10, 20, 40], phases=4):
        """生成正弦相位偏移图案"""
        patterns = []
        x = np.arange(width)
        y = np.arange(height)
        xx, yy = np.meshgrid(x, y)
        
        for freq in frequencies:
            for phase in range(phases):
                phi = 2 * np.pi * phase / phases
                pattern = 127.5 + 127.5 * np.sin(2 * np.pi * freq * xx / width + phi)
                patterns.append(pattern.astype(np.uint8))
        
        return patterns

3. 相机标定与极线校正

python 复制代码
class StereoCalibration:
    def __init__(self):
        self.camera_matrix1 = None
        self.dist_coeffs1 = None
        self.camera_matrix2 = None
        self.dist_coeffs2 = None
        self.R = None  # 旋转矩阵
        self.T = None  # 平移向量
        self.E = None  # 本质矩阵
        self.F = None  # 基础矩阵
    
    def calibrate(self, image_points1, image_points2, board_size, square_size):
        """双目标定"""
        # 生成物体点 (0,0,0), (1,0,0), (2,0,0) ..., (board_size[0]-1, board_size[1]-1,0)
        objp = np.zeros((board_size[0] * board_size[1], 3), np.float32)
        objp[:, :2] = np.mgrid[0:board_size[0], 0:board_size[1]].T.reshape(-1, 2)
        objp *= square_size
        
        obj_points = [objp] * len(image_points1)
        
        # 单目标定
        ret1, mtx1, dist1, rvecs1, tvecs1 = cv2.calibrateCamera(
            obj_points, image_points1, image_points1[0].shape[::-1], None, None)
        
        ret2, mtx2, dist2, rvecs2, tvecs2 = cv2.calibrateCamera(
            obj_points, image_points2, image_points2[0].shape[::-1], None, None)
        
        # 双目标定
        flags = cv2.CALIB_FIX_INTRINSIC
        ret, M1, d1, M2, d2, R, T, E, F = cv2.stereoCalibrate(
            obj_points, image_points1, image_points2,
            mtx1, dist1, mtx2, dist2, image_points1[0].shape[::-1],
            flags=flags)
        
        self.camera_matrix1 = M1
        self.dist_coeffs1 = d1
        self.camera_matrix2 = M2
        self.dist_coeffs2 = d2
        self.R = R
        self.T = T
        self.E = E
        self.F = F
        
        return ret
    
    def rectify(self, image_size):
        """计算校正映射"""
        R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify(
            self.camera_matrix1, self.dist_coeffs1,
            self.camera_matrix2, self.dist_coeffs2,
            image_size, self.R, self.T)
        
        # 计算校正映射
        map1x, map1y = cv2.initUndistortRectifyMap(
            self.camera_matrix1, self.dist_coeffs1, R1, P1,
            image_size, cv2.CV_32FC1)
        
        map2x, map2y = cv2.initUndistortRectifyMap(
            self.camera_matrix2, self.dist_coeffs2, R2, P2,
            image_size, cv2.CV_32FC1)
        
        return (map1x, map1y), (map2x, map2y), Q

4. 相位解包裹与深度计算

python 复制代码
class PhaseProcessing:
    @staticmethod
    def compute_phase(images, phases=4):
        """从相位偏移图像计算相位"""
        if len(images) != phases:
            raise ValueError(f"需要{phases}幅图像,但得到了{len(images)}")
        
        numerator = 0.0
        denominator = 0.0
        
        for k, img in enumerate(images):
            img_float = img.astype(np.float32)
            numerator += img_float * np.sin(2 * np.pi * k / phases)
            denominator += img_float * np.cos(2 * np.pi * k / phases)
        
        phase = np.arctan2(numerator, denominator)
        return phase
    
    @staticmethod
    def unwrap_phase(gray_code_patterns, phase):
        """使用格雷码解包裹相位"""
        # 将格雷码转换为二进制码
        code = np.zeros_like(phase, dtype=np.int32)
        
        for i, pattern in enumerate(gray_code_patterns):
            code += (pattern > 127) * (2 ** i)
        
        # 解包裹相位
        unwrapped_phase = phase + 2 * np.pi * code
        return unwrapped_phase
    
    @staticmethod
    def compute_depth(phase1, phase2, Q, baseline, wavelength):
        """从双相机相位计算深度"""
        # 计算相位差
        phase_diff = phase1 - phase2
        
        # 避免相位跳变
        phase_diff = np.where(phase_diff < -np.pi, phase_diff + 2*np.pi, phase_diff)
        phase_diff = np.where(phase_diff > np.pi, phase_diff - 2*np.pi, phase_diff)
        
        # 计算深度
        depth = (wavelength * baseline) / (2 * np.pi) * phase_diff
        
        return depth

5. 图像交叉融合算法

python 复制代码
class ImageFusion:
    @staticmethod
    def pyramid_fusion(img1, img2, levels=5):
        """基于金字塔的图像融合"""
        # 生成高斯金字塔
        gauss_pyr1 = [img1.astype(np.float32)]
        gauss_pyr2 = [img2.astype(np.float32)]
        
        for i in range(levels):
            img1 = cv2.pyrDown(gauss_pyr1[-1])
            img2 = cv2.pyrDown(gauss_pyr2[-1])
            gauss_pyr1.append(img1)
            gauss_pyr2.append(img2)
        
        # 生成拉普拉斯金字塔
        laplacian_pyr1 = [gauss_pyr1[levels-1]]
        laplacian_pyr2 = [gauss_pyr2[levels-1]]
        
        for i in range(levels-1, 0, -1):
            expanded1 = cv2.pyrUp(gauss_pyr1[i])
            expanded2 = cv2.pyrUp(gauss_pyr2[i])
            
            l1 = gauss_pyr1[i-1] - expanded1
            l2 = gauss_pyr2[i-1] - expanded2
            
            laplacian_pyr1.append(l1)
            laplacian_pyr2.append(l2)
        
        # 融合拉普拉斯金字塔
        fused_pyr = []
        for l1, l2 in zip(laplacian_pyr1, laplacian_pyr2):
            fused = (l1 + l2) / 2
            fused_pyr.append(fused)
        
        # 重建融合图像
        fused_img = fused_pyr[0]
        for i in range(1, levels):
            fused_img = cv2.pyrUp(fused_img)
            fused_img = fused_img + fused_pyr[i]
        
        return np.clip(fused_img, 0, 255).astype(np.uint8)
    
    @staticmethod
    def wavelet_fusion(img1, img2, wavelet='db1', level=3):
        """基于小波的图像融合"""
        import pywt
        
        # 将图像转换为浮点型
        img1 = img1.astype(np.float32)
        img2 = img2.astype(np.float32)
        
        # 多通道处理
        if len(img1.shape) == 3:
            fused_channels = []
            for c in range(img1.shape[2]):
                coeffs1 = pywt.wavedec2(img1[:,:,c], wavelet, level=level)
                coeffs2 = pywt.wavedec2(img2[:,:,c], wavelet, level=level)
                
                # 融合系数 - 这里使用简单的平均策略
                fused_coeffs = []
                for (c1, c2) in zip(coeffs1, coeffs2):
                    if isinstance(c1, tuple):  # 细节系数
                        fused = tuple((a+b)/2 for a, b in zip(c1, c2))
                    else:  # 近似系数
                        fused = (c1 + c2) / 2
                    fused_coeffs.append(fused)
                
                # 重建图像
                fused_channel = pywt.waverec2(fused_coeffs, wavelet)
                fused_channels.append(fused_channel)
            
            # 合并通道
            fused_img = np.stack(fused_channels, axis=-1)
        else:
            # 单通道图像
            coeffs1 = pywt.wavedec2(img1, wavelet, level=level)
            coeffs2 = pywt.wavedec2(img2, wavelet, level=level)
            
            # 融合系数
            fused_coeffs = []
            for (c1, c2) in zip(coeffs1, coeffs2):
                if isinstance(c1, tuple):
                    fused = tuple((a+b)/2 for a, b in zip(c1, c2))
                else:
                    fused = (c1 + c2) / 2
                fused_coeffs.append(fused)
            
            fused_img = pywt.waverec2(fused_coeffs, wavelet)
        
        # 归一化并转换为uint8
        fused_img = np.clip(fused_img, 0, 255)
        return fused_img.astype(np.uint8)

6. 完整系统集成

python 复制代码
class StructuredLight3DScanner:
    def __init__(self, cam1_index=0, cam2_index=1):
        self.camera_system = DualCameraSystem(cam1_index, cam2_index)
        self.calibration = StereoCalibration()
        self.is_calibrated = False
        self.rectify_maps = None
        self.Q = None
    
    def calibrate_system(self, checkerboard_size, square_size, num_images=15):
        """使用棋盘格标定系统"""
        obj_points = []  # 3D点
        img_points1 = []  # 相机1的2D点
        img_points2 = []  # 相机2的2D点
        
        pattern_found = 0
        
        while pattern_found < num_images:
            frame1, frame2 = self.camera_system.capture_synchronized()
            
            gray1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
            gray2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
            
            ret1, corners1 = cv2.findChessboardCorners(
                gray1, checkerboard_size, None)
            ret2, corners2 = cv2.findChessboardCorners(
                gray2, checkerboard_size, None)
            
            if ret1 and ret2:
                # 提高角点检测精度
                criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
                cv2.cornerSubPix(gray1, corners1, (11,11), (-1,-1), criteria)
                cv2.cornerSubPix(gray2, corners2, (11,11), (-1,-1), criteria)
                
                img_points1.append(corners1)
                img_points2.append(corners2)
                pattern_found += 1
                
                # 可视化
                cv2.drawChessboardCorners(frame1, checkerboard_size, corners1, ret1)
                cv2.drawChessboardCorners(frame2, checkerboard_size, corners2, ret2)
                cv2.imshow('Camera 1', frame1)
                cv2.imshow('Camera 2', frame2)
                cv2.waitKey(500)
        
        cv2.destroyAllWindows()
        
        # 执行标定
        image_size = gray1.shape[::-1]
        self.calibration.calibrate(img_points1, img_points2, 
                                  checkerboard_size, square_size)
        
        # 计算校正映射
        self.rectify_maps, _, self.Q = self.calibration.rectify(image_size)
        self.is_calibrated = True
    
    def capture_patterns(self, patterns):
        """捕获结构光图案序列"""
        all_images1 = []
        all_images2 = []
        
        for pattern in patterns:
            # 在实际系统中,这里应该控制投影仪显示pattern
            print(f"投影图案: {pattern.shape}")
            
            # 等待投影稳定
            time.sleep(0.5)
            
            # 捕获图像
            frame1, frame2 = self.camera_system.capture_synchronized()
            gray1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
            gray2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
            
            all_images1.append(gray1)
            all_images2.append(gray2)
        
        return all_images1, all_images2
    
    def reconstruct_3d(self, images1, images2, wavelength=40):
        """三维重建"""
        if not self.is_calibrated:
            raise RuntimeError("系统未标定")
        
        # 校正图像
        rectified1 = []
        rectified2 = []
        
        map1x, map1y = self.rectify_maps[0]
        map2x, map2y = self.rectify_maps[1]
        
        for img1, img2 in zip(images1, images2):
            r1 = cv2.remap(img1, map1x, map1y, cv2.INTER_LINEAR)
            r2 = cv2.remap(img2, map2x, map2y, cv2.INTER_LINEAR)
            rectified1.append(r1)
            rectified2.append(r2)
        
        # 计算相位
        phase_processor = PhaseProcessing()
        
        # 假设前4幅是相位偏移图案
        phase1 = phase_processor.compute_phase(rectified1[:4])
        phase2 = phase_processor.compute_phase(rectified2[:4])
        
        # 假设后8幅是格雷码图案(水平+垂直)
        gray_code_patterns1 = rectified1[4:12]
        gray_code_patterns2 = rectified2[4:12]
        
        # 解包裹相位
        unwrapped_phase1 = phase_processor.unwrap_phase(gray_code_patterns1[:4], phase1)
        unwrapped_phase2 = phase_processor.unwrap_phase(gray_code_patterns2[:4], phase2)
        
        # 计算深度
        baseline = np.linalg.norm(self.calibration.T)  # 基线距离
        depth = phase_processor.compute_depth(
            unwrapped_phase1, unwrapped_phase2, self.Q, baseline, wavelength)
        
        return depth
    
    def fuse_images(self, images1, images2, method='pyramid'):
        """融合双相机图像"""
        fused_images = []
        
        for img1, img2 in zip(images1, images2):
            if method == 'pyramid':
                fused = ImageFusion.pyramid_fusion(img1, img2)
            elif method == 'wavelet':
                fused = ImageFusion.wavelet_fusion(img1, img2)
            else:
                raise ValueError("不支持的融合方法")
            
            fused_images.append(fused)
        
        return fused_images
    
    def close(self):
        """关闭系统"""
        self.camera_system.release()

使用示例

python 复制代码
if __name__ == "__main__":
    # 初始化扫描仪
    scanner = StructuredLight3DScanner()
    
    try:
        # 标定系统
        print("开始标定...")
        scanner.calibrate_system((7,9), 0.025)  # 7x9棋盘格,每个方格25mm
        print("标定完成")
        
        # 生成结构光图案
        pattern_gen = StructuredLightPatterns()
        patterns = []
        patterns.extend(pattern_gen.generate_sinusoidal_patterns(1280, 720))
        patterns.extend(pattern_gen.generate_gray_code_patterns(1280, 720, bits=4))
        
        # 捕获图案
        print("捕获结构光图案...")
        images1, images2 = scanner.capture_patterns(patterns)
        
        # 图像融合
        print("图像融合...")
        fused_images = scanner.fuse_images(images1, images2, method='pyramid')
        
        # 三维重建
        print("三维重建...")
        depth_map = scanner.reconstruct_3d(images1, images2)
        
        # 可视化结果
        cv2.imshow("融合图像", fused_images[0])
        cv2.imshow("深度图", cv2.normalize(depth_map, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8))
        cv2.waitKey(0)
        
    finally:
        scanner.close()

关键技术说明

  1. 结构光编码

    • 使用格雷码和正弦相位偏移图案进行编码
    • 格雷码提供粗定位,正弦图案提供精确定位
  2. 相位解包裹

    • 结合格雷码和相位偏移技术解决相位模糊问题
    • 实现高精度的相位测量
  3. 立体匹配

    • 利用双相机系统获取不同视角的相位信息
    • 通过相位差计算三维坐标
  4. 图像融合

    • 金字塔融合:保留不同频率的信息
    • 小波融合:在频域实现图像融合
  5. 系统标定

    • 精确的相机内参和外参标定
    • 极线校正简化匹配过程

实际应用中的注意事项

  1. 硬件同步

    • 使用硬件触发器确保相机和投影仪的精确同步
    • 考虑曝光时间和投影时序
  2. 环境光干扰

    • 在暗室环境中工作或使用窄带滤光片
    • 考虑环境光补偿算法
  3. 标定精度

    • 使用高精度标定板
    • 多次标定取平均值提高精度
  4. 实时性优化

    • 使用GPU加速计算密集型操作
    • 优化算法实现实时处理

这个实现提供了双相机结构光系统的完整框架,包括图像采集、系统标定、结构光解码、三维重建和图像融合等功能。根据具体应用需求,可以进一步优化和扩展各个模块。

相关推荐
大米2H3 小时前
Jupyter lab 配置两个python环境
ide·python·jupyter
猎嘤一号4 小时前
使用 PyTorch 和 TensorBoard 实时可视化模型训练
人工智能·pytorch·python
Takina~5 小时前
python打卡day49
python
Frankabcdefgh5 小时前
Python基础数据类型与运算符全面解析
开发语言·数据结构·python·面试
是梦终空5 小时前
Python毕业设计226—基于python+爬虫+html的豆瓣影视数据可视化系统(源代码+数据库+万字论文)
爬虫·python·html·毕业设计·毕业论文·源代码·豆瓣影视数据可视化
kaiaaaa5 小时前
算法训练第十五天
开发语言·python·算法
whoarethenext6 小时前
使用 C/C++ 和 OpenCV 提取图像的感兴趣区域 (ROI)
c语言·c++·opencv
小玺玺6 小时前
[RDK X5] MJPG编解码开发实战:从官方API到OpenWanderary库的C++/Python实现
c++·python·opencv·rdk x5
s153356 小时前
12-OPENCV ROCKX项目 人脸拍照
人工智能·opencv·计算机视觉
zhuiQiuMX6 小时前
力扣LFU460
python·leetcode