加载背景图时Bounding Box Y坐标计算错误及图像缩放修复求助
修复PyQt6加载外部图像时的Bounding Box坐标与缩放问题
核心问题拆解
- 外部图像加载时未按等比例缩放适配控件,导致显示尺寸与原图比例失衡,后续坐标映射完全错误
- PyQt控件坐标系Y轴向下,而OpenCV图像坐标系Y轴向上,两者未做转换,导致Y坐标颠倒
- 拖拽绘制的边界框直接使用控件坐标,未映射回原图实际尺寸,保存后在OpenCV中位置偏差
修复方案与代码修改
1. 图像加载与等比例缩放处理
加载外部图像时,计算控件与原图的比例,按最小比例缩放,同时计算图像在控件中的居中偏移量,保存这些参数用于后续坐标转换。
2. 坐标映射逻辑
将控件上的鼠标坐标,通过缩放比例和偏移量转换为原图坐标,同时在保存时翻转Y轴适配OpenCV的坐标系。
3. 修改后的完整代码
import sys from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QFileDialog from PyQt6.QtGui import QPainter, QPen, QImage, QPixmap from PyQt6.QtCore import Qt, QRectF import cv2 import numpy as np class BBoxDrawer(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Bounding Box Drawer") self.setGeometry(100, 100, 800, 600) self.canvas = CanvasWidget(self) self.setCentralWidget(self.canvas) self.canvas.setFocusPolicy(Qt.FocusPolicy.StrongFocus) class CanvasWidget(QWidget): def __init__(self, parent): super().__init__(parent) self.bbox_mode = False self.start_point = None self.current_rect = None self.bboxes = [] self.bg_image = None self.scaled_image = None self.scale_ratio = 1.0 self.image_offset = (0, 0) # 图像在控件中的偏移量 def keyPressEvent(self, event): if event.key() == Qt.Key.Key_B: self.bbox_mode = not self.bbox_mode if not self.bbox_mode: self.current_rect = None self.update() def mousePressEvent(self, event): if self.bbox_mode and self.bg_image is not None: img_pos = self.widget_to_image_pos(event.pos()) self.start_point = img_pos self.current_rect = QRectF(img_pos.x(), img_pos.y(), 0, 0) def mouseMoveEvent(self, event): if self.bbox_mode and self.start_point is not None and self.bg_image is not None: img_pos = self.widget_to_image_pos(event.pos()) self.current_rect = QRectF( min(self.start_point.x(), img_pos.x()), min(self.start_point.y(), img_pos.y()), abs(self.start_point.x() - img_pos.x()), abs(self.start_point.y() - img_pos.y()) ) self.update() def mouseReleaseEvent(self, event): if self.bbox_mode and self.current_rect is not None: if self.current_rect.width() > 5 and self.current_rect.height() > 5: x1 = int(self.current_rect.left()) y1 = int(self.current_rect.top()) x2 = int(self.current_rect.right()) y2 = int(self.current_rect.bottom()) self.bboxes.append((x1, y1, x2, y2)) self.current_rect = None self.start_point = None self.update() def paintEvent(self, event): painter = QPainter(self) painter.fillRect(self.rect(), Qt.GlobalColor.white) if self.scaled_image is not None: painter.drawPixmap( self.image_offset[0], self.image_offset[1], self.scaled_image.width(), self.scaled_image.height(), self.scaled_image ) if self.current_rect is not None and self.bg_image is not None: widget_rect = self.image_to_widget_rect(self.current_rect) pen = QPen(Qt.GlobalColor.red, 2, Qt.PenStyle.DashLine) painter.setPen(pen) painter.drawRect(widget_rect) for bbox in self.bboxes: x1, y1, x2, y2 = bbox img_rect = QRectF(x1, y1, x2 - x1, y2 - y1) widget_rect = self.image_to_widget_rect(img_rect) pen = QPen(Qt.GlobalColor.blue, 2) painter.setPen(pen) painter.drawRect(widget_rect) def load_background(self, file_path): self.bg_image = QImage(file_path) if self.bg_image.isNull(): return widget_width = self.width() widget_height = self.height() img_width = self.bg_image.width() img_height = self.bg_image.height() # 计算等比例缩放比例 scale_width = widget_width / img_width scale_height = widget_height / img_height self.scale_ratio = min(scale_width, scale_height) # 生成缩放后的图像 scaled_width = int(img_width * self.scale_ratio) scaled_height = int(img_height * self.scale_ratio) self.scaled_image = QPixmap.fromImage(self.bg_image.scaled( scaled_width, scaled_height, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation )) # 计算居中偏移量 self.image_offset = ( (widget_width - scaled_width) // 2, (widget_height - scaled_height) // 2 ) self.update() def widget_to_image_pos(self, widget_pos): # 控件坐标转原图坐标 x = (widget_pos.x() - self.image_offset[0]) / self.scale_ratio y = (widget_pos.y() - self.image_offset[1]) / self.scale_ratio return QRectF(x, y, 0, 0).topLeft() def image_to_widget_rect(self, image_rect): # 原图坐标转控件坐标 x = self.image_offset[0] + image_rect.left() * self.scale_ratio y = self.image_offset[1] + image_rect.top() * self.scale_ratio width = image_rect.width() * self.scale_ratio height = image_rect.height() * self.scale_ratio return QRectF(x, y, width, height) def save_bboxes(self, file_path): if not self.bboxes or self.bg_image is None: return img = cv2.imread(file_path) img_height = img.shape[0] for bbox in self.bboxes: x1, y1, x2, y2 = bbox # 转换PyQt的顶向下Y坐标为OpenCV的底向上Y坐标 y1_cv = img_height - y1 y2_cv = img_height - y2 cv2.rectangle(img, (x1, min(y1_cv, y2_cv)), (x2, max(y1_cv, y2_cv)), (0, 255, 0), 2) cv2.imwrite("bbox_visualization.jpg", img) if __name__ == "__main__": app = QApplication(sys.argv) window = BBoxDrawer() window.show() file_path, _ = QFileDialog.getOpenFileName(window, "Open Image", "", "Image Files (*.png *.jpg *.jpeg)") if file_path: window.canvas.load_background(file_path) # 绘制完成后调用保存方法 # window.canvas.save_bboxes(file_path) sys.exit(app.exec())
关键修改说明
- 等比例缩放:使用
KeepAspectRatio模式保持图像比例,避免拉伸变形 - 坐标映射:新增双向坐标转换方法,确保绘制的边界框坐标与原图尺寸匹配
- Y轴适配:保存时翻转Y轴,对齐OpenCV的坐标系逻辑
- 居中显示:计算图像在控件中的偏移量,保证图像居中,坐标转换更准确
验证方法
- 加载外部图像,按B键进入绘制模式拖拽绘制边界框
- 调用
save_bboxes方法生成可视化结果 - 打开
bbox_visualization.jpg,边界框应准确覆盖目标区域
内容的提问来源于stack exchange,提问作者KansaiRobot
相关产品推荐
相关产品推荐

