1
This commit is contained in:
@@ -503,6 +503,8 @@ class CameraWorker(QThread):
|
|||||||
self._splat_radius = 0
|
self._splat_radius = 0
|
||||||
self._d2c_method = "sdk"
|
self._d2c_method = "sdk"
|
||||||
self._capture_opts: dict = {}
|
self._capture_opts: dict = {}
|
||||||
|
self._hdr_enabled = False
|
||||||
|
self._hdr_pending = None # None | bool,待应用的 HDR 开关
|
||||||
|
|
||||||
def set_d2c(self, enabled: bool):
|
def set_d2c(self, enabled: bool):
|
||||||
with self._lock:
|
with self._lock:
|
||||||
@@ -516,6 +518,10 @@ class CameraWorker(QThread):
|
|||||||
with self._lock:
|
with self._lock:
|
||||||
self._splat_radius = radius
|
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):
|
def trigger_capture(self, save_dir: str, capture_opts: dict | None = None):
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._capture_dir = save_dir
|
self._capture_dir = save_dir
|
||||||
@@ -538,6 +544,7 @@ class CameraWorker(QThread):
|
|||||||
OBStreamType, OBSensorType, OBFrameType, OBFormat,
|
OBStreamType, OBSensorType, OBFrameType, OBFormat,
|
||||||
OBFrameAggregateOutputMode,
|
OBFrameAggregateOutputMode,
|
||||||
save_point_cloud_to_ply,
|
save_point_cloud_to_ply,
|
||||||
|
HDRMergeFilter, OBPropertyID, OBPermissionType, OBHdrConfig,
|
||||||
)
|
)
|
||||||
from utils import frame_to_bgr_image
|
from utils import frame_to_bgr_image
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
@@ -643,6 +650,21 @@ class CameraWorker(QThread):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass # 部分固件版本不支持,忽略
|
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)
|
align_filter = AlignFilter(align_to_stream=OBStreamType.COLOR_STREAM)
|
||||||
point_cloud_filter = PointCloudFilter()
|
point_cloud_filter = PointCloudFilter()
|
||||||
|
|
||||||
@@ -666,6 +688,32 @@ class CameraWorker(QThread):
|
|||||||
cap_dir = self._capture_dir
|
cap_dir = self._capture_dir
|
||||||
splat_r = self._splat_radius
|
splat_r = self._splat_radius
|
||||||
cap_opts = self._capture_opts
|
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:
|
if self._camera_params is None:
|
||||||
@@ -711,8 +759,18 @@ class CameraWorker(QThread):
|
|||||||
continue
|
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()
|
cf = frames.get_color_frame()
|
||||||
df = frames.get_depth_frame()
|
df = display_frames.get_depth_frame()
|
||||||
if not cf or not df:
|
if not cf or not df:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -761,6 +819,19 @@ class CameraWorker(QThread):
|
|||||||
self.disconnected.emit()
|
self.disconnected.emit()
|
||||||
time.sleep(0.1)
|
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:
|
try:
|
||||||
pipeline.stop()
|
pipeline.stop()
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -1098,6 +1169,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.resize(_sz(1300), _sz(860))
|
self.resize(_sz(1300), _sz(860))
|
||||||
|
|
||||||
self._worker: CameraWorker | None = None
|
self._worker: CameraWorker | None = None
|
||||||
|
self._stopping_workers: list = [] # 保持引用直到线程真正结束
|
||||||
self._batch_worker: BatchD2CWorker | None = None
|
self._batch_worker: BatchD2CWorker | None = None
|
||||||
self._fetch_worker: FetchParamsWorker | None = None
|
self._fetch_worker: FetchParamsWorker | None = None
|
||||||
self._refresh_worker: RefreshDevicesWorker | None = None
|
self._refresh_worker: RefreshDevicesWorker | None = None
|
||||||
@@ -1318,6 +1390,12 @@ class MainWindow(QMainWindow):
|
|||||||
self.d2c_check.stateChanged.connect(
|
self.d2c_check.stateChanged.connect(
|
||||||
lambda s: self._worker and self._worker.set_d2c(s == Qt.Checked))
|
lambda s: self._worker and self._worker.set_d2c(s == Qt.Checked))
|
||||||
row_ses.addWidget(self.d2c_check)
|
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)
|
vg3.addLayout(row_ses)
|
||||||
|
|
||||||
# 行 3:采集内容复选框
|
# 行 3:采集内容复选框
|
||||||
@@ -1587,6 +1665,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._refresh_worker = RefreshDevicesWorker()
|
self._refresh_worker = RefreshDevicesWorker()
|
||||||
self._refresh_worker.done.connect(self._on_refresh_done)
|
self._refresh_worker.done.connect(self._on_refresh_done)
|
||||||
self._refresh_worker.error.connect(self._on_refresh_error)
|
self._refresh_worker.error.connect(self._on_refresh_error)
|
||||||
|
self._refresh_worker.finished.connect(self._on_refresh_finished)
|
||||||
self._refresh_worker.start()
|
self._refresh_worker.start()
|
||||||
|
|
||||||
def _on_refresh_done(self, result: dict):
|
def _on_refresh_done(self, result: dict):
|
||||||
@@ -1627,7 +1706,6 @@ class MainWindow(QMainWindow):
|
|||||||
self.device_status.setStyleSheet(style)
|
self.device_status.setStyleSheet(style)
|
||||||
self.d2c_device_status.setText(msg)
|
self.d2c_device_status.setText(msg)
|
||||||
self.d2c_device_status.setStyleSheet(style)
|
self.d2c_device_status.setStyleSheet(style)
|
||||||
self._refresh_worker = None
|
|
||||||
|
|
||||||
def _on_refresh_error(self, msg: str):
|
def _on_refresh_error(self, msg: str):
|
||||||
self.refresh_btn.setEnabled(True)
|
self.refresh_btn.setEnabled(True)
|
||||||
@@ -1638,7 +1716,12 @@ class MainWindow(QMainWindow):
|
|||||||
self.device_status.setStyleSheet(style)
|
self.device_status.setStyleSheet(style)
|
||||||
self.d2c_device_status.setText(full)
|
self.d2c_device_status.setText(full)
|
||||||
self.d2c_device_status.setStyleSheet(style)
|
self.d2c_device_status.setStyleSheet(style)
|
||||||
|
|
||||||
|
def _on_refresh_finished(self):
|
||||||
|
w = self._refresh_worker
|
||||||
self._refresh_worker = None
|
self._refresh_worker = None
|
||||||
|
if w:
|
||||||
|
w.deleteLater()
|
||||||
|
|
||||||
# ─────────────────────────────────
|
# ─────────────────────────────────
|
||||||
# Stream Toggle
|
# Stream Toggle
|
||||||
@@ -1660,21 +1743,32 @@ class MainWindow(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
self._stop_stream()
|
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):
|
def _start_stream(self, color_cfg: dict, depth_cfg: dict):
|
||||||
old = self._worker
|
old = self._worker
|
||||||
self._worker = None
|
self._worker = None
|
||||||
if old and old.isRunning():
|
if old:
|
||||||
# 异步等旧 pipeline 释放后再启动新的
|
self._retire_worker(old)
|
||||||
old.request_stop()
|
|
||||||
old.finished.connect(old.deleteLater)
|
pending = self._stopping_workers
|
||||||
old.finished.connect(lambda: self._do_start_stream(color_cfg, depth_cfg))
|
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.capture_btn.setEnabled(False)
|
||||||
self.color_combo.setEnabled(False)
|
self.color_combo.setEnabled(False)
|
||||||
self.depth_combo.setEnabled(False)
|
self.depth_combo.setEnabled(False)
|
||||||
self.stream_btn.setText("关闭")
|
else:
|
||||||
return
|
|
||||||
if old:
|
|
||||||
old.deleteLater()
|
|
||||||
self._do_start_stream(color_cfg, depth_cfg)
|
self._do_start_stream(color_cfg, depth_cfg)
|
||||||
|
|
||||||
def _do_start_stream(self, color_cfg: dict, depth_cfg: dict):
|
def _do_start_stream(self, color_cfg: dict, depth_cfg: dict):
|
||||||
@@ -1683,6 +1777,8 @@ class MainWindow(QMainWindow):
|
|||||||
self._worker.set_d2c(self.d2c_check.isChecked())
|
self._worker.set_d2c(self.d2c_check.isChecked())
|
||||||
self._worker.set_d2c_method(self.capture_d2c_method_combo.currentData())
|
self._worker.set_d2c_method(self.capture_d2c_method_combo.currentData())
|
||||||
self._worker.set_splat(self.capture_splat_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.preview_ready.connect(self._update_preview)
|
||||||
self._worker.capture_done.connect(self._on_capture_done)
|
self._worker.capture_done.connect(self._on_capture_done)
|
||||||
self._worker.params_ready.connect(self._on_params_ready)
|
self._worker.params_ready.connect(self._on_params_ready)
|
||||||
@@ -1696,10 +1792,8 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def _stop_stream(self):
|
def _stop_stream(self):
|
||||||
if self._worker:
|
if self._worker:
|
||||||
w = self._worker
|
self._retire_worker(self._worker)
|
||||||
self._worker = None
|
self._worker = None
|
||||||
w.request_stop()
|
|
||||||
w.finished.connect(w.deleteLater)
|
|
||||||
self.capture_btn.setEnabled(False)
|
self.capture_btn.setEnabled(False)
|
||||||
self.color_combo.setEnabled(True)
|
self.color_combo.setEnabled(True)
|
||||||
self.depth_combo.setEnabled(True)
|
self.depth_combo.setEnabled(True)
|
||||||
@@ -1818,6 +1912,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._fetch_worker = FetchParamsWorker(cc, dc)
|
self._fetch_worker = FetchParamsWorker(cc, dc)
|
||||||
self._fetch_worker.log.connect(self._append_d2c_log)
|
self._fetch_worker.log.connect(self._append_d2c_log)
|
||||||
self._fetch_worker.params_ready.connect(self._on_fetch_params_ready)
|
self._fetch_worker.params_ready.connect(self._on_fetch_params_ready)
|
||||||
|
self._fetch_worker.finished.connect(self._on_fetch_finished)
|
||||||
self._fetch_worker.start()
|
self._fetch_worker.start()
|
||||||
|
|
||||||
def _on_fetch_params_ready(self, params: dict, path: str):
|
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[1].tolist()}")
|
||||||
self._append_d2c_log(f" {R[2].tolist()}")
|
self._append_d2c_log(f" {R[2].tolist()}")
|
||||||
self._append_d2c_log(f" Extrinsic t : {t.tolist()} (mm)")
|
self._append_d2c_log(f" Extrinsic t : {t.tolist()} (mm)")
|
||||||
|
|
||||||
|
def _on_fetch_finished(self):
|
||||||
|
w = self._fetch_worker
|
||||||
self._fetch_worker = None
|
self._fetch_worker = None
|
||||||
|
if w:
|
||||||
|
w.deleteLater()
|
||||||
|
|
||||||
def _resolve_params_file(self) -> str | None:
|
def _resolve_params_file(self) -> str | None:
|
||||||
explicit = self.params_path_edit.text().strip()
|
explicit = self.params_path_edit.text().strip()
|
||||||
@@ -1911,12 +2011,19 @@ class MainWindow(QMainWindow):
|
|||||||
method=method, splat_radius=splat_r)
|
method=method, splat_radius=splat_r)
|
||||||
self._batch_worker.log.connect(self.d2c_log.append)
|
self._batch_worker.log.connect(self.d2c_log.append)
|
||||||
self._batch_worker.done.connect(self._on_batch_done)
|
self._batch_worker.done.connect(self._on_batch_done)
|
||||||
|
self._batch_worker.finished.connect(self._on_batch_finished)
|
||||||
self._batch_worker.start()
|
self._batch_worker.start()
|
||||||
|
|
||||||
def _on_batch_done(self):
|
def _on_batch_done(self):
|
||||||
self.run_d2c_btn.setEnabled(True)
|
self.run_d2c_btn.setEnabled(True)
|
||||||
self.d2c_log.append("\n[ 转换完成 ]")
|
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):
|
def _append_d2c_log(self, msg: str):
|
||||||
self.d2c_log.append(msg)
|
self.d2c_log.append(msg)
|
||||||
|
|
||||||
@@ -1942,6 +2049,9 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
self._stop_stream()
|
self._stop_stream()
|
||||||
|
# 等待所有后台 worker 真正退出,防止 QThread destroyed while running
|
||||||
|
for w in list(self._stopping_workers):
|
||||||
|
w.wait(3000)
|
||||||
super().closeEvent(event)
|
super().closeEvent(event)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user