From 426dbe191de13660b38ae07ad2a4b1d444b20fcf Mon Sep 17 00:00:00 2001 From: newbieQQ Date: Mon, 6 Jul 2026 15:05:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E9=87=8F=E6=A8=A1=E5=BC=8F=EF=BC=9A?= =?UTF-8?q?=E4=BB=8E=E4=BB=BB=E6=84=8F=E5=B8=A7=E5=BC=80=E5=A7=8B=20+=20?= =?UTF-8?q?=E5=B7=A6=E5=8F=B3=E9=94=AE=E5=AF=BC=E8=88=AA=20+=20=E8=B7=B3?= =?UTF-8?q?=E8=BF=87=E5=B8=A7=E8=AE=B0=E5=BD=950?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 131 +++++++++++++++++++++++++------------------------------- 1 file changed, 59 insertions(+), 72 deletions(-) diff --git a/main.py b/main.py index e396575..ed1a1be 100644 --- a/main.py +++ b/main.py @@ -71,6 +71,7 @@ class FrameView(QLabel): self._block_clicks: bool = False self.on_point_selected = None + self.on_key_pressed = None # 键盘回调 @property def reference_ready(self) -> bool: @@ -130,6 +131,11 @@ class FrameView(QLabel): if self.on_point_selected: self.on_point_selected(self._select_mode, pt) + def keyPressEvent(self, event) -> None: + if self.on_key_pressed: + self.on_key_pressed(event.key()) + super().keyPressEvent(event) + def paintEvent(self, event) -> None: super().paintEvent(event) painter = QPainter(self) @@ -235,6 +241,7 @@ class MainWindow(QMainWindow): self.frame_view = FrameView() self.frame_view.on_point_selected = self._on_point_changed + self.frame_view.on_key_pressed = self._on_key_pressed # 右侧面板(弹性宽度) panel = QWidget() @@ -418,7 +425,7 @@ class MainWindow(QMainWindow): f"{self._video.fps:.1f} fps") def _show_frame(self, idx: int) -> None: - """随机访问显示帧(手动导航用)""" + """显示指定帧""" if self._video is None: return frame = self._video.read_frame(idx) @@ -431,20 +438,6 @@ class MainWindow(QMainWindow): self.slider.blockSignals(False) self._update_display() - def _show_frame_seq(self, idx: int) -> None: - """顺序读取显示帧(测量模式用,不 seek)""" - if self._video is None: - return - frame = self._video.read_next() - if frame is None: - return - self._current_frame_idx = idx - self.frame_view.set_frame(frame) - self.slider.blockSignals(True) - self.slider.setValue(idx) - self.slider.blockSignals(False) - self._update_display() - def _update_display(self) -> None: self.lbl_frame.setText(str(self._current_frame_idx)) o, m, m2 = self.frame_view.point_o, self.frame_view.point_m, self.frame_view.point_m2 @@ -498,12 +491,12 @@ class MainWindow(QMainWindow): QMessageBox.warning(self, "警告", "请先在首帧选定 O 和 M") return - # 重置测量 self._measurements.clear() self._prev_angle = None self._measuring = True self.frame_view._select_mode = "m2" self.frame_view._block_clicks = False + self.frame_view.setFocus() # 接收键盘事件 # 禁用无关控件 self.btn_start.setEnabled(False) @@ -514,51 +507,38 @@ class MainWindow(QMainWindow): self.btn_reset_all.setEnabled(False) self.btn_reset_m2.setEnabled(False) - # 重开视频,从第 0 帧开始顺序读取 - self._video.reset() - self.frame_view.reset_m2() - self._show_frame_seq(0) - self.lbl_hint.setText("⏳ 点击 M2 → 自动进入下一帧") + # 从当前帧开始 + self._show_frame(self._current_frame_idx) + self.lbl_hint.setText("⏳ 点击 M2 或按 → 跳过") self.lbl_hint.setStyleSheet( "color: #0af; font-size: 12px; font-weight: bold;") self._update_display() - def _stop_measuring(self) -> None: - self._measuring = False - self.frame_view._block_clicks = False - self.btn_start.setEnabled(True) - self.btn_stop.setEnabled(False) - self.slider.setEnabled(True) - self.btn_prev.setEnabled(True) - self.btn_next.setEnabled(True) - self.btn_reset_all.setEnabled(True) - self.btn_reset_m2.setEnabled(True) - self.lbl_hint.setText( - f"已停止 · 共 {len(self._measurements)} 帧数据") - self.lbl_hint.setStyleSheet("color: #888; font-size: 11px;") - self._update_display() - - def _go_to_frame(self, idx: int) -> None: - """测量模式中顺序前进并清空 M2""" - self.frame_view.reset_m2() - self._show_frame_seq(idx) - - # ══════════════ 选点回调 ═════════════════════════════ - - def _on_point_changed(self, mode: str, pt: tuple) -> None: - x, y = int(round(pt[0])), int(round(pt[1])) - if mode == "o": - self.lbl_o.setText(f"({x}, {y})") - self.lbl_hint.setText("点击画面选 M") - elif mode == "m": - self.lbl_m.setText(f"({x}, {y})") - if not self._measuring: - self.lbl_hint.setText("点击画面选 M2,或点「开始测量」") - elif mode == "m2": - self.lbl_m2.setText(f"({x}, {y})") - self._update_display() - if self._measuring: + def _on_key_pressed(self, key: int) -> None: + if not self._measuring: + return + if key == Qt.Key.Key_Right: + if self.frame_view.point_m2 is not None: self._record_and_advance() + else: + self._record_skip() + elif key == Qt.Key.Key_Left: + if self._current_frame_idx > 0: + self._current_frame_idx -= 1 + self._show_frame(self._current_frame_idx) + + def _record_skip(self) -> None: + """跳过当前帧,记录 0""" + o = self.frame_view.point_o + m = self.frame_view.point_m + if o is None or m is None: + return + self._measurements.append(( + self._current_frame_idx, 0.0, o[0], o[1], + )) + self._prev_angle = 0.0 + QApplication.processEvents() + QTimer.singleShot(50, self._advance_frame) def _record_and_advance(self) -> None: o = self.frame_view.point_o @@ -574,8 +554,6 @@ class MainWindow(QMainWindow): self._measurements.append(( self._current_frame_idx, angle, m2[0], m2[1], )) - - # 先保存当前角度为上帧,再进下一帧 self._prev_angle = angle QApplication.processEvents() QTimer.singleShot(150, self._advance_frame) @@ -592,6 +570,26 @@ class MainWindow(QMainWindow): return self._go_to_frame(next_idx) + def _go_to_frame(self, idx: int) -> None: + """测量模式中跳转并清空 M2""" + self.frame_view.reset_m2() + self._show_frame(idx) + + def _stop_measuring(self) -> None: + self._measuring = False + self.frame_view._block_clicks = False + self.btn_start.setEnabled(True) + self.btn_stop.setEnabled(False) + self.slider.setEnabled(True) + self.btn_prev.setEnabled(True) + self.btn_next.setEnabled(True) + self.btn_reset_all.setEnabled(True) + self.btn_reset_m2.setEnabled(True) + self.lbl_hint.setText( + f"已停止 · 共 {len(self._measurements)} 帧数据") + self.lbl_hint.setStyleSheet("color: #888; font-size: 11px;") + self._update_display() + # ══════════════ 方向矫正 ═════════════════════════════ def _on_flip_toggled(self, checked: bool) -> None: @@ -642,11 +640,6 @@ class MainWindow(QMainWindow): frames_dir = path.parent / f"{path.stem}_frames" frames_dir.mkdir(exist_ok=True) - # 用顺序读取导出(避免 seek 卡死) - self._video.reset() - frame_buf = self._video.read_next() - buf_idx = 0 - with open(path, "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow([ @@ -656,18 +649,12 @@ class MainWindow(QMainWindow): "screenshot", ]) for frame_idx, angle, m2x, m2y in self._measurements: - # 跳过不匹配的帧(理论不会发生) - while buf_idx < frame_idx and frame_buf is not None: - frame_buf = self._video.read_next() - buf_idx += 1 - - if frame_buf is not None and buf_idx == frame_idx: + frame = self._video.read_frame(frame_idx) + if frame is not None: annotated = self._draw_annotations( - frame_buf, o, m, (m2x, m2y)) + frame, o, m, (m2x, m2y)) img_name = f"frame_{frame_idx:04d}.png" cv2.imwrite(str(frames_dir / img_name), annotated) - frame_buf = self._video.read_next() - buf_idx += 1 else: img_name = ""