3. 构建视觉感知模块

import numpy as np
import cv2
import open3d as o3d
from scipy.spatial.transform import Rotation as R

class VisualPerceptionModule:
    def __init__(self, camera_intrinsics, camera_extrinsics_to_base):
        """
        camera_intrinsics: dict with fx, fy, cx, cy, width, height
        camera_extrinsics_to_base: 4x4 matrix (camera -> base)
        """
        self.fx = camera_intrinsics['fx']
        self.fy = camera_intrinsics['fy']
        self.cx = camera_intrinsics['cx']
        self.cy = camera_intrinsics['cy']
        self.width = camera_intrinsics['width']
        self.height = camera_intrinsics['height']
        self.T_cam_to_base = camera_extrinsics_to_base

    def detect_objects(self, rgb_image, depth_image, object_labels=None):
        """
        Detect objects using color segmentation (simple approach for demo)
        Returns list of dicts: {label, position_base (3,), orientation_quat (4,)}
        """
        # Convert to HSV for color-based segmentation
        hsv = cv2.cvtColor(rgb_image, cv2.COLOR_BGR2HSV)
        
        # Define color ranges for common objects (red, green, blue, yellow)
        color_ranges = {
            'red': ([0, 100, 100], [10, 255, 255]),
            'green': ([40, 100, 100], [80, 255, 255]),
            'blue': ([100, 100, 100], [130, 255, 255]),
            'yellow': ([20, 100, 100], [35, 255, 255])
        }
        
        detected = []
        for label, (lower, upper) in color_ranges.items():
            if object_labels and label not in object_labels:
                continue
            mask = cv2.inRange(hsv, np.array(lower), np.array(upper))
            # Morphological ops to clean mask
            mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((5,5), np.uint8))
            mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((5,5), np.uint8))
            
            contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            for cnt in contours:
                area = cv2.contourArea(cnt)
                if area < 500:  # filter small noise
                    continue
                # Get bounding box
                x, y, w, h = cv2.boundingRect(cnt)
                center_u = x + w // 2
                center_v = y + h // 2
                
                # Get depth at center (use median of small patch for robustness)
                patch = depth_image[max(0,center_v-2):center_v+3, max(0,center_u-2):center_u+3]
                valid_depths = patch[patch > 0]
                if len(valid_depths) == 0:
                    continue
                depth_val = np.median(valid_depths) / 1000.0  # if depth in mm, convert to meters
                
                # Back-project to camera coordinates
                z = depth_val
                x_cam = (center_u - self.cx) * z / self.fx
                y_cam = (center_v - self.cy) * z / self.fy
                point_cam = np.array([x_cam, y_cam, z, 1.0])
                
                # Transform to base frame
                point_base = self.T_cam_to_base @ point_cam
                position = point_base[:3]
                
                # Estimate orientation: assume object is upright, yaw from contour orientation
                rect = cv2.minAreaRect(cnt)
                angle = rect[2]  # degrees
                # Convert to quaternion (assuming roll=0, pitch=0, yaw=angle)
                quat = R.from_euler('xyz', [0, 0, np.deg2rad(angle)]).as_quat()  # [x,y,z,w]
                
                detected.append({
                    'label': label,
                    'position_base': position,
                    'orientation_quat': quat,
                    'bbox_2d': (x, y, w, h),
                    'confidence': area / (self.width * self.height)
                })
        
        return detected

    def get_object_pose_in_base(self, rgb_image, depth_image, target_label=None):
        """High-level API returning pose in base frame"""
        detections = self.detect_objects(rgb_image, depth_image, 
                                        object_labels=[target_label] if target_label else None)
        if not detections:
            return None
        # Return the largest detection if multiple
        best = max(detections, key=lambda d: d['confidence'])
        return {
            'label': best['label'],
            'position': best['position_base'],
            'orientation': best['orientation_quat']
        }

# Example usage (to be integrated with simulation)
if __name__ == "__main__":
    # Mock intrinsics (e.g., from simulation)
    intrinsics = {'fx': 500.0, 'fy': 500.0, 'cx': 320.0, 'cy': 240.0, 'width': 640, 'height': 480}
    # Mock extrinsic: camera mounted 0.5m above base, looking down
    T_cam_to_base = np.eye(4)
    T_cam_to_base[:3, 3] = [0.0, 0.0, 0.5]  # camera origin in base
    # Rotate camera to look down (90 deg around X)
    T_cam_to_base[:3, :3] = R.from_euler('xyz', [np.pi/2, 0, 0]).as_matrix()
    
    perception = VisualPerceptionModule(intrinsics, T_cam_to_base)
    
    # In real simulation, get these from env
    # rgb = env.get_rgb()
    # depth = env.get_depth()
    # result = perception.get_object_pose_in_base(rgb, depth, 'red')
    # print(result)