diff --git a/main.py b/main.py index fb1343b..98237b6 100644 --- a/main.py +++ b/main.py @@ -51,7 +51,7 @@ from PyQt5.QtWidgets import ( from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer from PyQt5.QtGui import QImage, QPixmap, QFont -from d2c_batch import batch_convert +from d2c_batch import batch_convert, software_d2c, software_d2c_inv PARAMS_FILE = "camera_params.json" MIN_DEPTH_MM, MAX_DEPTH_MM = 200, 5000 @@ -435,12 +435,17 @@ class CameraWorker(QThread): self._disconnect_emitted = False self._sync_wait_count = 0 # 等待同步帧的计数器 self._splat_radius = 0 + self._d2c_method = "sdk" self._capture_opts: dict = {} def set_d2c(self, enabled: bool): with self._lock: self._d2c = enabled + def set_d2c_method(self, method: str): + with self._lock: + self._d2c_method = method + def set_splat(self, radius: int): with self._lock: self._splat_radius = radius @@ -586,11 +591,12 @@ class CameraWorker(QThread): self._consec_timeouts = 0 with self._lock: - d2c = self._d2c - do_cap = self._do_capture - cap_dir = self._capture_dir - splat_r = self._splat_radius - cap_opts = self._capture_opts + d2c = self._d2c + d2c_method = self._d2c_method + do_cap = self._do_capture + cap_dir = self._capture_dir + splat_r = self._splat_radius + cap_opts = self._capture_opts # 首帧提取相机参数 if self._camera_params is None: @@ -636,14 +642,8 @@ class CameraWorker(QThread): continue # 预览 - view = frames - if d2c: - af = align_filter.process(frames) - if af: - view = af.as_frame_set() - - cf = view.get_color_frame() - df = view.get_depth_frame() + cf = frames.get_color_frame() + df = frames.get_depth_frame() if not cf or not df: continue @@ -651,11 +651,31 @@ class CameraWorker(QThread): if color_bgr is None: continue - depth_data = np.frombuffer(df.get_data(), dtype=np.uint16).reshape( - df.get_height(), df.get_width()) - if splat_r > 0: - depth_data = _splat_depth(depth_data, splat_r) - depth_vis = depth_to_vis(depth_data, df.get_depth_scale()) + if d2c: + if d2c_method == "forward" and self._camera_params is not None: + raw = np.frombuffer(df.get_data(), dtype=np.uint16).reshape( + df.get_height(), df.get_width()) + depth_data = software_d2c( + raw, self._camera_params, splat_radius=splat_r) + depth_scale = df.get_depth_scale() + else: + af = align_filter.process(frames) + if af: + view = af.as_frame_set() + df2 = view.get_depth_frame() + if df2: + df = df2 + depth_data = np.frombuffer(df.get_data(), dtype=np.uint16).reshape( + df.get_height(), df.get_width()) + depth_scale = df.get_depth_scale() + else: + depth_data = np.frombuffer(df.get_data(), dtype=np.uint16).reshape( + df.get_height(), df.get_width()) + if splat_r > 0: + depth_data = _splat_depth(depth_data, splat_r) + depth_scale = df.get_depth_scale() + + depth_vis = depth_to_vis(depth_data, depth_scale) now = time.monotonic() if now - self._last_emit_ts >= 0.033: @@ -717,40 +737,59 @@ class CameraWorker(QThread): imwrite_utf8(str(base / "depth_vis.png"), depth_to_vis(raw, df.get_depth_scale())) - need_aligned = (capture_opts.get("d2c", True) or + d2c_method = capture_opts.get("d2c_method", "sdk") + need_aligned = ((d2c_method == "sdk" and capture_opts.get("d2c", True)) or capture_opts.get("pc", False) or capture_opts.get("pc_color", False)) + aset = None if need_aligned: aligned = align_filter.process(frames) if aligned: aset = aligned.as_frame_set() - adf = aset.get_depth_frame() - if adf and capture_opts.get("d2c", True): + + # D2C 深度图保存 + if capture_opts.get("d2c", True): + if d2c_method == "sdk" and aset: + adf = aset.get_depth_frame() + if adf: ad = np.frombuffer(adf.get_data(), dtype=np.uint16).reshape( adf.get_height(), adf.get_width()) imwrite_utf8(str(base / "depth_d2c.png"), ad) imwrite_utf8(str(base / "depth_d2c_vis.png"), depth_to_vis(ad, adf.get_depth_scale())) + elif self._camera_params is not None: + raw = np.frombuffer(df.get_data(), dtype=np.uint16).reshape( + df.get_height(), df.get_width()) + if d2c_method == "inverse": + aligned_img = software_d2c_inv(raw, self._camera_params) + else: # forward + splat_r = capture_opts.get("splat_radius", 1) + aligned_img = software_d2c(raw, self._camera_params, splat_radius=splat_r) + imwrite_utf8(str(base / "depth_d2c.png"), aligned_img) + imwrite_utf8(str(base / "depth_d2c_vis.png"), + depth_to_vis(aligned_img, df.get_depth_scale())) - if capture_opts.get("pc", False): - try: - point_cloud_filter.set_create_point_format(OBFormat.POINT) - pc = point_cloud_filter.process(aligned) - if pc: - save_ply_utf8(str(base / "point_cloud.ply"), pc, - save_point_cloud_to_ply) - except Exception as exc: - self.log_msg.emit(f"点云警告:{exc}") + # 点云保存(始终使用 SDK AlignFilter 结果) + if aset: + if capture_opts.get("pc", False): + try: + point_cloud_filter.set_create_point_format(OBFormat.POINT) + pc = point_cloud_filter.process(aligned) + if pc: + save_ply_utf8(str(base / "point_cloud.ply"), pc, + save_point_cloud_to_ply) + except Exception as exc: + self.log_msg.emit(f"点云警告:{exc}") - if capture_opts.get("pc_color", False): - try: - point_cloud_filter.set_create_point_format(OBFormat.RGB_POINT) - pc = point_cloud_filter.process(aligned) - if pc: - save_ply_utf8(str(base / "point_cloud_color.ply"), pc, - save_point_cloud_to_ply) - except Exception as exc: - self.log_msg.emit(f"彩色点云警告:{exc}") + if capture_opts.get("pc_color", False): + try: + point_cloud_filter.set_create_point_format(OBFormat.RGB_POINT) + pc = point_cloud_filter.process(aligned) + if pc: + save_ply_utf8(str(base / "point_cloud_color.ply"), pc, + save_point_cloud_to_ply) + except Exception as exc: + self.log_msg.emit(f"彩色点云警告:{exc}") self.capture_done.emit(save_dir) @@ -1062,6 +1101,15 @@ class MainWindow(QMainWindow): for key, cb in self._capture_checks.items(): cb.setChecked(getattr(self._app_settings, f"capture_{key}", cb.isChecked())) + def _on_capture_d2c_method_changed(self): + method = self.capture_d2c_method_combo.currentData() + if self._worker: + self._worker.set_d2c_method(method) + is_forward = method == "forward" + if not is_forward: + self.capture_splat_check.setChecked(False) + self.capture_splat_check.setEnabled(is_forward) + def _on_capture_splat_changed(self, state: int): radius = max(1, self._app_settings.splat_radius) if state == Qt.Checked else 0 if self._worker: @@ -1147,7 +1195,7 @@ class MainWindow(QMainWindow): layout.addWidget(grp2) # ── 第 3 步:保存目录 & 采集内容 ── - grp3 = self._step_group(3, "保存目录 & 采集内容") + grp3 = self._step_group(3, "保存目录 && 采集内容") vg3 = QVBoxLayout(grp3) vg3.setContentsMargins(8, 4, 8, 8) vg3.setSpacing(5) @@ -1223,6 +1271,25 @@ class MainWindow(QMainWindow): row_cap.addStretch() vg3.addLayout(row_cap) + # 行 4:D2C 转换方式 + row_d2c_method = QHBoxLayout() + row_d2c_method.setSpacing(8) + lbl_d2c_method = QLabel("D2C 方式:") + lbl_d2c_method.setFixedWidth(_sz(58)) + row_d2c_method.addWidget(lbl_d2c_method) + self.capture_d2c_method_combo = QComboBox() + self.capture_d2c_method_combo.setSizeAdjustPolicy(QComboBox.AdjustToContents) + self.capture_d2c_method_combo.addItem("逆向投影(SDK 同等,推荐)", "sdk") + self.capture_d2c_method_combo.addItem("正向投影 + Splat 填充", "forward") + self.capture_d2c_method_combo.currentIndexChanged.connect( + self._on_capture_d2c_method_changed) + row_d2c_method.addWidget(self.capture_d2c_method_combo) + # 默认选中 "sdk",Splat 应取消勾选并禁用 + self.capture_splat_check.setChecked(False) + self.capture_splat_check.setEnabled(False) + row_d2c_method.addStretch() + vg3.addLayout(row_d2c_method) + layout.addWidget(grp3) # ── 预览区 ── @@ -1611,6 +1678,8 @@ class MainWindow(QMainWindow): self.capture_btn.setEnabled(False) self.capture_btn.setText("拍摄中…") capture_opts = {k: cb.isChecked() for k, cb in self._capture_checks.items()} + capture_opts["d2c_method"] = self.capture_d2c_method_combo.currentData() + capture_opts["splat_radius"] = self._app_settings.splat_radius self._worker.trigger_capture(str(self._session_dir / ts), capture_opts) def _on_capture_done(self, save_dir: str):