2D图像物体朝向检测问题:PCA适配3D模型替换失效
解决YOLO检测后PCA计算物体朝向偏差及Three.js适配问题
问题背景
我需要完成以下流程:用YOLO检测图像中的沙发和桌子,通过LaMa移除原物体后,将3D模型放置在原位置并保持与原物体一致的朝向。目前采用OpenCV的PCA方法计算物体朝向,但该方法仅在部分图像中有效;将计算得到的角度传给Three.js前端后,出现两个问题:
- 沙发朝向存在轻微偏差
- 桌子与原物体朝向完全相反(原场景中沙发与桌子相对摆放,替换后二者同向)
现有实现代码:
import cv2 import numpy as np from ultralytics import YOLO from math import atan2, cos, sin, sqrt, pi import matplotlib.pyplot as plt from PIL import Image from matplotlib.patches import Polygon img = cv2.imread("/home/spxlpt087/Downloads/image_with_info.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) model = YOLO("/home/spxlpt087/Downloads/yoloV11_1_orig_2.pt") result = model.predict(img, conf = 0.45) cordinates = result[0].boxes.xywh.cpu().numpy() roi_new = img[int(y1):int(y1)+int(h), int(x1):int(x1)+int(w)] mask_cordinates = result[0].masks.xy[1] height, width = img.shape[:2] binary_mask = np.zeros((height, width), dtype=np.uint8) polygon_points = np.int32(mask_cordinates) cv2.fillPoly(binary_mask, [polygon_points], color=255) contours, _ = cv2.findContours(binary_mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) contour_image = img.copy() # Replace `img` with your original image cv2.drawContours(contour_image, contours, -1, (0, 255, 0), 2) for c in contours: sz = len(c) data_pts = np.empty((sz, 2), dtype = np.float64) for i in range(data_pts.shape[0]): data_pts[i,0] = c[i,0,0] data_pts[i,1] = c[i,0,1] mean = np.empty((0)) mean, eigenvectors, eigenvalues = cv2.PCACompute2(data_pts, mean) angle_g = np.arctan2(eigenvectors[0, 1], eigenvectors[0, 0]) #* 180 / np.pi angle_doc = atan2(eigenvectors[0,1], eigenvectors[0,0]) # Store the center of the object cntr = (int(mean[0,0]), int(mean[0,1])) cv2.circle(contour_image, cntr, 5, (0, 0, 255), 20) # Red circle for center # Plot the data points for pt in data_pts: pt_int = tuple(map(int, pt)) cv2.circle(contour_image, pt_int, 2, (255, 0, 0), 20) plt.figure(figsize=(10, 8)) plt.imshow(cv2.cvtColor(contour_image, cv2.COLOR_BGR2RGB)) plt.title("Contour with PCA Center and Data Points") plt.axis('off') plt.show()
问题分析
PCA朝向计算的局限性:
- PCA仅基于轮廓的主轴分布,当物体轮廓对称(如方形桌子)、轮廓提取有噪声,或物体主轴与实际朝向不匹配(比如沙发靠背方向不是主轴)时,会导致方向判断错误。
- 代码直接取
result[0].masks.xy[1],未结合YOLO类别信息,可能误处理其他物体的mask,导致角度计算错误。
坐标系与旋转方向不匹配:
- 图像坐标系(y轴向下)与Three.js 3D坐标系(y轴向上)存在方向差异,直接传递角度会导致旋转方向反转。
- OpenCV中PCA计算的角度基于x轴逆时针旋转,而Three.js旋转方向通常为顺时针,二者规则不一致。
缺乏类别针对性处理:
- 沙发和桌子的形状特征不同,统一用PCA计算朝向会出现适配问题:比如桌子的长边是主要朝向,而沙发的靠背方向才是关键,PCA可能误取沙发短边作为主轴。
解决方案
一、优化PCA朝向计算逻辑
1. 结合YOLO类别信息处理对应物体
遍历YOLO检测结果,针对沙发/桌子类别单独提取mask和bbox,避免误处理其他物体:
# 遍历每个检测到的物体,替换为你的类别ID SOFA_CLS_ID = 0 TABLE_CLS_ID = 1 for i, box in enumerate(result[0].boxes): cls = box.cls.cpu().numpy()[0] if cls not in [SOFA_CLS_ID, TABLE_CLS_ID]: continue # 获取当前物体的mask和bbox mask_cordinates = result[0].masks.xy[i] x1, y1, w, h = box.xywh.cpu().numpy()[0] # 后续轮廓处理和PCA计算逻辑放在这里
2. 预处理轮廓减少噪声影响
在提取轮廓前,对mask做平滑和形态学处理,去除噪声点:
polygon_points = np.int32(mask_cordinates) # 简化mask多边形,减少冗余点 approx_mask = cv2.approxPolyDP(polygon_points, 0.01*cv2.arcLength(polygon_points, True), True) # 闭运算填充小空洞 kernel = np.ones((5,5), np.uint8) binary_mask = cv2.morphologyEx(binary_mask, cv2.MORPH_CLOSE, kernel) cv2.fillPoly(binary_mask, [approx_mask], color=255)
3. 结合物体宽高比校正主轴方向
计算PCA主轴后,对比物体bbox的宽高,确保主轴方向与实际长边一致:
mean, eigenvectors, eigenvalues = cv2.PCACompute2(data_pts, mean) major_axis_len = eigenvalues[0][0] minor_axis_len = eigenvalues[1][0] bbox_width = w bbox_height = h # 如果主轴长度小于短边,说明方向反转,调整角度 if (major_axis_len < minor_axis_len) or ((bbox_width > bbox_height) and (abs(eigenvectors[0][0]) < abs(eigenvectors[0][1]))): angle_doc += np.pi # 交换特征向量,修正主轴方向 eigenvectors = eigenvectors[[1,0]]
二、适配Three.js的坐标系与旋转规则
1. 转换图像坐标系到Three.js坐标系
图像y轴向下,Three.js y轴向上,需调整角度方向:
# 将弧度转换为角度,并反转旋转方向适配Three.js threejs_angle = -angle_doc * 180 / np.pi # 针对桌子类别额外反转180度(根据实际场景调整) if cls == TABLE_CLS_ID: threejs_angle += 180 # 确保角度在0-360度范围内 threejs_angle = threejs_angle % 360
2. 前端旋转适配
Three.js中模型旋转基于Y轴,若模型初始朝向与检测基准不一致,需添加偏移校正:
// Three.js中设置模型绕Y轴旋转 const rotationY = THREE.MathUtils.degToRad(threejs_angle); model.rotation.y = rotationY; // 若模型初始朝向有偏移,可添加固定值,比如+90度 // model.rotation.y = rotationY + THREE.MathUtils.degToRad(90);
三、验证与调试方法
- 在图像上绘制PCA主轴,验证是否与物体实际朝向一致:
axis_length = max(major_axis_len, minor_axis_len) / 2 pt1 = (cntr[0] - int(axis_length * eigenvectors[0][0]), cntr[1] - int(axis_length * eigenvectors[0][1])) pt2 = (cntr[0] + int(axis_length * eigenvectors[0][0]), cntr[1] + int(axis_length * eigenvectors[0][1])) cv2.line(contour_image, pt1, pt2, (255, 0, 0), 3)
- 打印每个物体的类别、计算的角度、转换后的Three.js角度,对比原场景朝向,总结类别-specific的校正规则。
内容的提问来源于stack exchange,提问作者Lalit Joshi
相关产品推荐
相关产品推荐

