This commit is contained in:
i
2026-04-22 16:00:26 +08:00
parent 278e9b073d
commit f4d4193025
+131 -21
View File
@@ -503,6 +503,8 @@ class CameraWorker(QThread):
self._splat_radius = 0
self._d2c_method = "sdk"
self._capture_opts: dict = {}
self._hdr_enabled = False
self._hdr_pending = None # None | bool,待应用的 HDR 开关
def set_d2c(self, enabled: bool):
with self._lock:
@@ -516,6 +518,10 @@ class CameraWorker(QThread):
with self._lock:
self._splat_radius = radius
def set_hdr(self, enabled: bool):
with self._lock:
self._hdr_pending = enabled
def trigger_capture(self, save_dir: str, capture_opts: dict | None = None):
with self._lock:
self._capture_dir = save_dir
@@ -538,6 +544,7 @@ class CameraWorker(QThread):
OBStreamType, OBSensorType, OBFrameType, OBFormat,
OBFrameAggregateOutputMode,
save_point_cloud_to_ply,
HDRMergeFilter, OBPropertyID, OBPermissionType, OBHdrConfig,
)
from utils import frame_to_bgr_image
except ImportError as exc:
@@ -643,6 +650,21 @@ class CameraWorker(QThread):
except Exception:
pass # 部分固件版本不支持,忽略
# HDR 支持检测
hdr_supported = False
hdr_use_interleave = False
hdr_filter = None
try:
dev = pipeline.get_device()
hdr_supported = dev.is_property_supported(
OBPropertyID.OB_STRUCT_DEPTH_HDR_CONFIG,
OBPermissionType.PERMISSION_READ_WRITE)
if hdr_supported:
hdr_use_interleave = dev.isFrameInterleaveSupported()
hdr_filter = HDRMergeFilter()
except Exception as exc:
self.log_msg.emit(f"HDR 检测警告:{exc}")
align_filter = AlignFilter(align_to_stream=OBStreamType.COLOR_STREAM)
point_cloud_filter = PointCloudFilter()
@@ -660,12 +682,38 @@ class CameraWorker(QThread):
self._consec_timeouts = 0
with self._lock:
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
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
hdr_pending = self._hdr_pending
if hdr_pending is not None:
self._hdr_pending = None
if hdr_pending is not None and hdr_supported:
try:
dev = pipeline.get_device()
if hdr_use_interleave:
if hdr_pending:
dev.loadFrameInterleave("Depth from HDR")
dev.set_bool_property(
OBPropertyID.OB_PROP_FRAME_INTERLEAVE_ENABLE_BOOL,
hdr_pending)
else:
cfg = OBHdrConfig()
cfg.enable = hdr_pending
if hdr_pending:
cfg.exposure_1 = 7500
cfg.gain_1 = 24
cfg.exposure_2 = 100
cfg.gain_2 = 16
dev.set_hdr_config(cfg)
self._hdr_enabled = hdr_pending
self.log_msg.emit(f"HDR {'已开启' if hdr_pending else '已关闭'}")
except Exception as exc:
self.log_msg.emit(f"HDR 切换失败:{exc}")
# 首帧提取相机参数
if self._camera_params is None:
@@ -711,8 +759,18 @@ class CameraWorker(QThread):
continue
# 预览
# HDR merge:用 merged 帧集替换深度帧
display_frames = frames
if self._hdr_enabled and hdr_filter is not None:
try:
merged = hdr_filter.process(frames)
if merged:
display_frames = merged.as_frame_set()
except Exception:
pass
cf = frames.get_color_frame()
df = frames.get_depth_frame()
df = display_frames.get_depth_frame()
if not cf or not df:
continue
@@ -761,6 +819,19 @@ class CameraWorker(QThread):
self.disconnected.emit()
time.sleep(0.1)
if self._hdr_enabled and hdr_supported:
try:
dev = pipeline.get_device()
if hdr_use_interleave:
dev.set_bool_property(
OBPropertyID.OB_PROP_FRAME_INTERLEAVE_ENABLE_BOOL, False)
else:
cfg = OBHdrConfig()
cfg.enable = False
dev.set_hdr_config(cfg)
except Exception:
pass
try:
pipeline.stop()
except Exception:
@@ -1098,6 +1169,7 @@ class MainWindow(QMainWindow):
self.resize(_sz(1300), _sz(860))
self._worker: CameraWorker | None = None
self._stopping_workers: list = [] # 保持引用直到线程真正结束
self._batch_worker: BatchD2CWorker | None = None
self._fetch_worker: FetchParamsWorker | None = None
self._refresh_worker: RefreshDevicesWorker | None = None
@@ -1318,6 +1390,12 @@ class MainWindow(QMainWindow):
self.d2c_check.stateChanged.connect(
lambda s: self._worker and self._worker.set_d2c(s == Qt.Checked))
row_ses.addWidget(self.d2c_check)
self.hdr_check = QCheckBox("HDR")
self.hdr_check.setToolTip("开启深度 HDR 合并(设备不支持时无效,见日志)")
self.hdr_check.setChecked(False)
self.hdr_check.stateChanged.connect(
lambda s: self._worker and self._worker.set_hdr(s == Qt.Checked))
row_ses.addWidget(self.hdr_check)
vg3.addLayout(row_ses)
# 行 3:采集内容复选框
@@ -1587,6 +1665,7 @@ class MainWindow(QMainWindow):
self._refresh_worker = RefreshDevicesWorker()
self._refresh_worker.done.connect(self._on_refresh_done)
self._refresh_worker.error.connect(self._on_refresh_error)
self._refresh_worker.finished.connect(self._on_refresh_finished)
self._refresh_worker.start()
def _on_refresh_done(self, result: dict):
@@ -1627,7 +1706,6 @@ class MainWindow(QMainWindow):
self.device_status.setStyleSheet(style)
self.d2c_device_status.setText(msg)
self.d2c_device_status.setStyleSheet(style)
self._refresh_worker = None
def _on_refresh_error(self, msg: str):
self.refresh_btn.setEnabled(True)
@@ -1638,7 +1716,12 @@ class MainWindow(QMainWindow):
self.device_status.setStyleSheet(style)
self.d2c_device_status.setText(full)
self.d2c_device_status.setStyleSheet(style)
def _on_refresh_finished(self):
w = self._refresh_worker
self._refresh_worker = None
if w:
w.deleteLater()
# ─────────────────────────────────
# Stream Toggle
@@ -1660,22 +1743,33 @@ class MainWindow(QMainWindow):
else:
self._stop_stream()
def _retire_worker(self, w: "CameraWorker"):
"""请求 worker 停止,持有引用直到线程真正结束再销毁。"""
self._stopping_workers.append(w)
w.request_stop()
w.finished.connect(lambda: self._on_worker_finished(w))
def _on_worker_finished(self, w: "CameraWorker"):
if w in self._stopping_workers:
self._stopping_workers.remove(w)
w.deleteLater()
def _start_stream(self, color_cfg: dict, depth_cfg: dict):
old = self._worker
self._worker = None
if old and old.isRunning():
# 异步等旧 pipeline 释放后再启动新的
old.request_stop()
old.finished.connect(old.deleteLater)
old.finished.connect(lambda: self._do_start_stream(color_cfg, depth_cfg))
if old:
self._retire_worker(old)
pending = self._stopping_workers
if pending:
# 等最后一个停止中的 worker 退出后再开新 pipeline
last = pending[-1]
last.finished.connect(lambda: self._do_start_stream(color_cfg, depth_cfg))
self.capture_btn.setEnabled(False)
self.color_combo.setEnabled(False)
self.depth_combo.setEnabled(False)
self.stream_btn.setText("关闭")
return
if old:
old.deleteLater()
self._do_start_stream(color_cfg, depth_cfg)
else:
self._do_start_stream(color_cfg, depth_cfg)
def _do_start_stream(self, color_cfg: dict, depth_cfg: dict):
preset = self.preset_combo.currentData()
@@ -1683,6 +1777,8 @@ class MainWindow(QMainWindow):
self._worker.set_d2c(self.d2c_check.isChecked())
self._worker.set_d2c_method(self.capture_d2c_method_combo.currentData())
self._worker.set_splat(self.capture_splat_combo.currentData())
if self.hdr_check.isChecked():
self._worker.set_hdr(True)
self._worker.preview_ready.connect(self._update_preview)
self._worker.capture_done.connect(self._on_capture_done)
self._worker.params_ready.connect(self._on_params_ready)
@@ -1696,10 +1792,8 @@ class MainWindow(QMainWindow):
def _stop_stream(self):
if self._worker:
w = self._worker
self._retire_worker(self._worker)
self._worker = None
w.request_stop()
w.finished.connect(w.deleteLater)
self.capture_btn.setEnabled(False)
self.color_combo.setEnabled(True)
self.depth_combo.setEnabled(True)
@@ -1818,6 +1912,7 @@ class MainWindow(QMainWindow):
self._fetch_worker = FetchParamsWorker(cc, dc)
self._fetch_worker.log.connect(self._append_d2c_log)
self._fetch_worker.params_ready.connect(self._on_fetch_params_ready)
self._fetch_worker.finished.connect(self._on_fetch_finished)
self._fetch_worker.start()
def _on_fetch_params_ready(self, params: dict, path: str):
@@ -1854,7 +1949,12 @@ class MainWindow(QMainWindow):
self._append_d2c_log(f" {R[1].tolist()}")
self._append_d2c_log(f" {R[2].tolist()}")
self._append_d2c_log(f" Extrinsic t : {t.tolist()} (mm)")
def _on_fetch_finished(self):
w = self._fetch_worker
self._fetch_worker = None
if w:
w.deleteLater()
def _resolve_params_file(self) -> str | None:
explicit = self.params_path_edit.text().strip()
@@ -1911,12 +2011,19 @@ class MainWindow(QMainWindow):
method=method, splat_radius=splat_r)
self._batch_worker.log.connect(self.d2c_log.append)
self._batch_worker.done.connect(self._on_batch_done)
self._batch_worker.finished.connect(self._on_batch_finished)
self._batch_worker.start()
def _on_batch_done(self):
self.run_d2c_btn.setEnabled(True)
self.d2c_log.append("\n[ 转换完成 ]")
def _on_batch_finished(self):
w = self._batch_worker
self._batch_worker = None
if w:
w.deleteLater()
def _append_d2c_log(self, msg: str):
self.d2c_log.append(msg)
@@ -1942,6 +2049,9 @@ class MainWindow(QMainWindow):
def closeEvent(self, event):
self._stop_stream()
# 等待所有后台 worker 真正退出,防止 QThread destroyed while running
for w in list(self._stopping_workers):
w.wait(3000)
super().closeEvent(event)