1
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user