【任务执行 11-15】第 12/25 课

🤖 第12课:物体识别

📌 物体识别概述

物体识别是服务机器人感知世界的核心能力——知道"是什么"才能决定"怎么处理":

👁️ 物体识别流水线

图像采集 → 预处理 → 特征提取 → 目标检测 → 分类定位
                                        ↓
                              多目标追踪 → 抓取姿态估计
                                        ↓
                                    任务执行

📌 目标检测模拟

import math, random

class SimpleObjectDetector:
    """简易物体检测器模拟"""
    def __init__(self):
        self.classes = {
            "cup": {"size": (0.08, 0.10), "color_range": ("白色","透明"), "shape": "圆柱"},
            "bottle": {"size": (0.06, 0.25), "color_range": ("透明","绿色"), "shape": "圆柱"},
            "document": {"size": (0.21, 0.30), "color_range": ("白色",), "shape": "矩形"},
            "package": {"size": (0.20, 0.30), "color_range": ("棕色","黄色"), "shape": "立方体"},
            "tray": {"size": (0.35, 0.02), "color_range": ("银色","黑色"), "shape": "扁平"},
            "phone": {"size": (0.07, 0.15), "color_range": ("黑色","白色"), "shape": "矩形"},
            "key": {"size": (0.03, 0.08), "color_range": ("银色","金色"), "shape": "不规则"},
            "medicine": {"size": (0.05, 0.08), "color_range": ("白色","橙色"), "shape": "矩形"},
        }
        self.detection_accuracy = 0.85
        self.confidence_range = (0.7, 0.99)

    def detect(self, scene_objects):
        """模拟物体检测"""
        results = []
        for obj in scene_objects:
            if random.random() < self.detection_accuracy:
                cls = obj.get("class", "unknown")
                x, y, w, h = obj.get("bbox", [0,0,0,0])
                confidence = random.uniform(*self.confidence_range)
                
                # 模拟误检
                if random.random() < 0.05:
                    wrong_classes = [c for c in self.classes if c != cls]
                    cls = random.choice(wrong_classes) if wrong_classes else cls
                    confidence *= 0.7
                
                results.append({
                    "class": cls,
                    "confidence": round(confidence, 3),
                    "bbox": [x, y, w, h],
                    "attributes": self.classes.get(cls, {})
                })
        return results

    def filter_by_confidence(self, results, threshold=0.5):
        return [r for r in results if r["confidence"] >= threshold]

random.seed(42)
detector = SimpleObjectDetector()

scene = [
    {"class": "cup", "bbox": [100, 200, 50, 60]},
    {"class": "document", "bbox": [300, 150, 80, 100]},
    {"class": "package", "bbox": [500, 300, 120, 100]},
    {"class": "bottle", "bbox": [200, 400, 40, 120]},
    {"class": "key", "bbox": [400, 250, 25, 40]},
    {"class": "medicine", "bbox": [600, 180, 30, 50]},
]

print("物体识别模拟")
print("=" * 55)
results = detector.detect(scene)
filtered = detector.filter_by_confidence(results)

for r in filtered:
    attrs = r["attributes"]
    size_info = f"尺寸{attrs['size']}" if attrs else ""
    print(f"  📦 {r['class']} (置信度:{r['confidence']:.1%}) bbox:{r['bbox']} {size_info}")

print(f"\n检测: {len(results)}个, 过滤后: {len(filtered)}个")
print("✅ 物体识别验证通过")
✅ 验证通过 物体识别模拟 ======================================================= 📦 cup (置信度:70.7%) bbox:[100, 200, 50, 60] 尺寸(0.08, 0.1) 📦 document (置信度:91.4%) bbox:[300, 150, 80, 100] 尺寸(0.21, 0.3) 📦 document (置信度:57.6%) bbox:[200, 400, 40, 120] 尺寸(0.21, 0.3) 📦 key (置信度:87.5%) bbox:[400, 250, 25, 40] 尺寸(0.03, 0.08) 📦 medicine (置信度:90.3%) bbox:[600, 180, 30, 50] 尺寸(0.05, 0.08) 检测: 5个, 过滤后: 5个 ✅ 物体识别验证通过

📌 多目标追踪

在动态场景中,需要持续追踪多个目标:

class ObjectTracker:
    """多目标追踪器"""
    def __init__(self):
        self.tracks = {}
        self.next_id = 0
        self.max_lost = 5
        self.iou_threshold = 0.3

    def _iou(self, box1, box2):
        """计算IoU (交并比)"""
        x1 = max(box1[0], box2[0])
        y1 = max(box1[1], box2[1])
        x2 = min(box1[0]+box1[2], box2[0]+box2[2])
        y2 = min(box1[1]+box1[3], box2[1]+box2[3])
        
        inter = max(0, x2-x1) * max(0, y2-y1)
        area1 = box1[2] * box1[3]
        area2 = box2[2] * box2[3]
        union = area1 + area2 - inter
        return inter / union if union > 0 else 0

    def update(self, detections):
        """更新追踪"""
        matched = set(); used_dets = set()
        
        # 匈牙利匹配(简化:贪心)
        for tid, track in list(self.tracks.items()):
            best_det = None; best_iou = self.iou_threshold
            for i, det in enumerate(detections):
                if i in used_dets: continue
                iou = self._iou(track["bbox"], det["bbox"])
                if iou > best_iou:
                    best_iou = iou; best_det = i
            if best_det is not None:
                self.tracks[tid]["bbox"] = detections[best_det]["bbox"]
                self.tracks[tid]["class"] = detections[best_det]["class"]
                self.tracks[tid]["lost"] = 0
                self.tracks[tid]["age"] += 1
                matched.add(tid); used_dets.add(best_det)
            else:
                self.tracks[tid]["lost"] += 1
                if self.tracks[tid]["lost"] > self.max_lost:
                    del self.tracks[tid]
        
        # 新检测创建新轨迹
        for i, det in enumerate(detections):
            if i not in used_dets:
                self.tracks[self.next_id] = {
                    "class": det["class"], "bbox": det["bbox"],
                    "lost": 0, "age": 1
                }
                self.next_id += 1

    def get_tracks(self):
        return {tid: t for tid, t in self.tracks.items() if t["lost"] == 0}

tracker = ObjectTracker()
print("多目标追踪模拟")
print("=" * 55)

frames = [
    [{"class":"cup","bbox":[100,200,50,60]},{"class":"bottle","bbox":[200,400,40,120]}],
    [{"class":"cup","bbox":[105,198,50,60]},{"class":"bottle","bbox":[203,395,40,120]},{"class":"document","bbox":[300,150,80,100]}],
    [{"class":"cup","bbox":[110,195,50,60]},{"class":"bottle","bbox":[208,390,40,120]},{"class":"document","bbox":[302,148,80,100]}],
    [{"class":"cup","bbox":[115,192,50,60]},{"class":"document","bbox":[305,146,80,100]}],
    [{"class":"cup","bbox":[120,189,50,60]},{"class":"document","bbox":[308,144,80,100]},{"class":"package","bbox":[500,300,120,100]}],
]

for frame_i, detections in enumerate(frames):
    tracker.update(detections)
    active = tracker.get_tracks()
    print(f"\n帧{frame_i+1}: 检测{len(detections)}个 → 追踪{len(active)}个")
    for tid, t in active.items():
        print(f"  轨迹{tid}: {t['class']} bbox{t['bbox']} age={t['age']}")

print("\n✅ 多目标追踪验证通过")
✅ 验证通过 多目标追踪模拟 ======================================================= 帧1: 检测2个 → 追踪2个 轨迹0: cup bbox[100, 200, 50, 60] age=1 轨迹1: bottle bbox[200, 400, 40, 120] age=1 帧2: 检测3个 → 追踪3个 轨迹0: cup bbox[105, 198, 50, 60] age=2 轨迹1: bottle bbox[203, 395, 40, 120] age=2 轨迹2: document bbox[300, 150, 80, 100] age=1 帧3: 检测3个 → 追踪3个 轨迹0: cup bbox[110, 195, 50, 60] age=3 轨迹1: bottle bbox[208, 390, 40, 120] age=3 轨迹2: document bbox[302, 148, 80, 100] age=2 帧4: 检测2个 → 追踪2个 轨迹0: cup bbox[115, 192, 50, 60] age=4 轨迹2: document bbox[305, 146, 80, 100] age=3 帧5: 检测3个 → 追踪3个 轨迹0: cup bbox[120, 189, 50, 60] age=5 轨迹2: document bbox[308, 144, 80, 100] age=4 轨迹3: package bbox[500, 300, 120, 100] age=1 ✅ 多目标追踪验证通过

📌 抓取姿态估计

识别物体后,需要估计如何抓取——接近方向、夹爪宽度、旋转角度:

class GraspPoseEstimator:
    """抓取姿态估计"""
    def __init__(self):
        self.object_grasp_rules = {
            "cup": {"approach": "top", "gripper_width": 0.08, "rotation": 0},
            "bottle": {"approach": "side", "gripper_width": 0.06, "rotation": 90},
            "document": {"approach": "top", "gripper_width": 0.02, "rotation": 0},
            "package": {"approach": "side", "gripper_width": 0.20, "rotation": 0},
            "key": {"approach": "top", "gripper_width": 0.03, "rotation": 45},
            "medicine": {"approach": "top", "gripper_width": 0.05, "rotation": 0},
            "tray": {"approach": "bottom", "gripper_width": 0.35, "rotation": 0},
        }
        self.gripper_max = 0.25

    def estimate(self, obj_class, obj_bbox, obj_pose=None):
        """估计抓取姿态"""
        rule = self.object_grasp_rules.get(obj_class)
        if not rule:
            return {"feasible": False, "reason": f"未知物体类型: {obj_class}"}
        
        if rule["gripper_width"] > self.gripper_max:
            return {"feasible": False, "reason": f"物体过大: 需要{rule['gripper_width']}m, 最大{self.gripper_max}m"}
        
        x, y, w, h = obj_bbox
        cx, cy = x + w/2, y + h/2
        
        grasp_pose = {
            "position": (cx, cy),
            "approach": rule["approach"],
            "rotation": rule["rotation"],
            "gripper_width": rule["gripper_width"],
            "feasible": True,
        }
        return grasp_pose

estimator = GraspPoseEstimator()
print("抓取姿态估计")
print("=" * 55)

objects = [
    ("cup", [100, 200, 50, 60]),
    ("bottle", [200, 400, 40, 120]),
    ("document", [300, 150, 80, 100]),
    ("package", [500, 300, 120, 100]),
    ("key", [400, 250, 25, 40]),
    ("tray", [150, 300, 200, 20]),
]

for cls, bbox in objects:
    result = estimator.estimate(cls, bbox)
    if result["feasible"]:
        print(f"\n✅ {cls}: 方向={result['approach']} 旋转={result['rotation']}° 夹爪={result['gripper_width']}m")
    else:
        print(f"\n❌ {cls}: {result['reason']}")

print("\n✅ 抓取姿态估计验证通过")
✅ 验证通过 抓取姿态估计 ======================================================= ✅ cup: 方向=top 旋转=0° 夹爪=0.08m ✅ bottle: 方向=side 旋转=90° 夹爪=0.06m ✅ document: 方向=top 旋转=0° 夹爪=0.02m ✅ package: 方向=side 旋转=0° 夹爪=0.2m ✅ key: 方向=top 旋转=45° 夹爪=0.03m ❌ tray: 物体过大: 需要0.35m, 最大0.25m ✅ 抓取姿态估计验证通过

📌 检测模型选型

📊 模型对比

模型速度精度适用
YOLOv8实时检测
DETR端到端检测
GroundingDINO开放词汇检测
SegmentAnything极高精细分割
MediaPipe极快边缘设备

📌 练习

📝 练习 1

实现开放词汇检测:支持用自然语言描述物体(如'红色的杯子'),结合颜色和形状特征进行检测。

📝 练习 2

实现3D物体姿态估计:从2D边界框和物体尺寸推断3D位置和朝向,用于抓取规划。

📝 练习 3

实现场景理解:不仅检测单个物体,还推断物体间关系('咖啡在杯子里'、'文件在桌上')。

📌 成就

🏆 本课成就

◀ 上一课 📚 目录 下一课 ▶