2025-08-19 08:30:48 +08:00
|
|
|
|
import os
|
|
|
|
|
import numpy as np
|
2025-08-19 18:15:56 +08:00
|
|
|
|
import cv2
|
2025-08-19 08:30:48 +08:00
|
|
|
|
from matplotlib.colors import LinearSegmentedColormap
|
|
|
|
|
|
2025-08-19 18:15:56 +08:00
|
|
|
|
|
2025-08-19 08:30:48 +08:00
|
|
|
|
class FemtoBoltViewer:
|
|
|
|
|
def __init__(self, depth_min=900, depth_max=1300):
|
|
|
|
|
self.depth_min = depth_min
|
|
|
|
|
self.depth_max = depth_max
|
|
|
|
|
|
2025-08-19 18:15:56 +08:00
|
|
|
|
# 自定义彩虹色 colormap
|
2025-08-19 08:30:48 +08:00
|
|
|
|
colors = ['fuchsia', 'red', 'yellow', 'lime', 'cyan', 'blue',
|
|
|
|
|
'fuchsia', 'red', 'yellow', 'lime', 'cyan', 'blue']
|
|
|
|
|
self.cmap = LinearSegmentedColormap.from_list("custom_cmap", colors)
|
|
|
|
|
|
2025-08-19 18:15:56 +08:00
|
|
|
|
# SDK 设备句柄和配置
|
2025-08-19 08:30:48 +08:00
|
|
|
|
self.device_handle = None
|
|
|
|
|
self.pykinect = None
|
|
|
|
|
self.config = None
|
|
|
|
|
|
2025-08-19 18:15:56 +08:00
|
|
|
|
# 缓存背景+网格图像(仅生成一次)
|
|
|
|
|
self.background = None
|
|
|
|
|
|
|
|
|
|
# OpenCV 窗口
|
|
|
|
|
cv2.namedWindow("Depth CV")
|
2025-08-19 08:30:48 +08:00
|
|
|
|
|
|
|
|
|
def _load_sdk(self):
|
2025-08-19 18:15:56 +08:00
|
|
|
|
"""加载并初始化 FemtoBolt SDK"""
|
2025-08-19 08:30:48 +08:00
|
|
|
|
try:
|
|
|
|
|
import pykinect_azure as pykinect
|
|
|
|
|
self.pykinect = pykinect
|
|
|
|
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
dll_path = os.path.join(base_dir, "..", "dll", "femtobolt", "bin", "k4a.dll")
|
|
|
|
|
self.pykinect.initialize_libraries(track_body=False, module_k4a_path=dll_path)
|
|
|
|
|
return True
|
|
|
|
|
except Exception as e:
|
2025-08-19 18:15:56 +08:00
|
|
|
|
print(f"加载 SDK 失败: {e}")
|
2025-08-19 08:30:48 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def _configure_device(self):
|
2025-08-19 18:15:56 +08:00
|
|
|
|
"""配置 FemtoBolt 深度相机"""
|
2025-08-19 08:30:48 +08:00
|
|
|
|
self.config = self.pykinect.default_configuration
|
|
|
|
|
self.config.depth_mode = self.pykinect.K4A_DEPTH_MODE_NFOV_UNBINNED
|
|
|
|
|
self.config.camera_fps = self.pykinect.K4A_FRAMES_PER_SECOND_15
|
|
|
|
|
self.config.synchronized_images_only = False
|
|
|
|
|
self.config.color_resolution = 0
|
|
|
|
|
self.device_handle = self.pykinect.start_device(config=self.config)
|
|
|
|
|
|
2025-08-19 18:15:56 +08:00
|
|
|
|
def _get_color_image(self, depth_image):
|
|
|
|
|
"""将原始深度图转换为叠加背景网格后的 RGB 彩色图像"""
|
|
|
|
|
h, w = depth_image.shape
|
|
|
|
|
# 第一次调用时生成灰色背景和白色网格
|
|
|
|
|
if self.background is None:
|
|
|
|
|
self.background = np.full((h, w, 3), 128, dtype=np.uint8) # 灰色 (0.5 -> 128)
|
|
|
|
|
# 绘制网格线
|
|
|
|
|
for x in range(w):
|
|
|
|
|
cv2.line(self.background, (x, 0), (x, h-1), (255, 255, 255), 1)
|
|
|
|
|
for y in range(h):
|
|
|
|
|
cv2.line(self.background, (0, y), (w-1, y), (255, 255, 255), 1)
|
|
|
|
|
|
|
|
|
|
# 生成深度掩码,仅保留指定范围内的像素
|
|
|
|
|
mask_valid = (depth_image >= self.depth_min) & (depth_image <= self.depth_max)
|
|
|
|
|
depth_clipped = np.clip(depth_image, self.depth_min, self.depth_max)
|
|
|
|
|
normed = (depth_clipped.astype(np.float32) - self.depth_min) / (self.depth_max - self.depth_min)
|
|
|
|
|
|
|
|
|
|
# 反转映射,保证颜色方向与之前一致
|
|
|
|
|
normed = 1.0 - normed
|
|
|
|
|
|
|
|
|
|
# 应用自定义 colormap,将深度值映射到 RGB
|
|
|
|
|
rgba = self.cmap(normed)
|
|
|
|
|
rgb = (rgba[..., :3] * 255).astype(np.uint8)
|
|
|
|
|
|
|
|
|
|
# 叠加:在背景上覆盖彩色深度图(掩码处不覆盖,保留灰色背景+网格)
|
|
|
|
|
final_img = self.background.copy()
|
|
|
|
|
final_img[mask_valid] = rgb[mask_valid]
|
|
|
|
|
return final_img
|
2025-08-19 08:30:48 +08:00
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
|
if not self._load_sdk():
|
2025-08-19 18:15:56 +08:00
|
|
|
|
print("SDK 加载失败,程序退出")
|
2025-08-19 08:30:48 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
self._configure_device()
|
2025-08-19 18:15:56 +08:00
|
|
|
|
print("FemtoBolt 深度相机启动成功,按 Ctrl+C 或 ESC 退出")
|
2025-08-19 08:30:48 +08:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
while True:
|
|
|
|
|
capture = self.device_handle.update()
|
|
|
|
|
if capture is None:
|
|
|
|
|
continue
|
|
|
|
|
ret, depth_image = capture.get_depth_image()
|
|
|
|
|
if not ret or depth_image is None:
|
|
|
|
|
continue
|
|
|
|
|
|
2025-08-19 18:15:56 +08:00
|
|
|
|
# 转换并渲染当前帧
|
|
|
|
|
final_img = self._get_color_image(depth_image)
|
|
|
|
|
|
|
|
|
|
# OpenCV 显示
|
|
|
|
|
cv2.imshow("Depth CV", final_img)
|
|
|
|
|
# 按 ESC 键退出
|
|
|
|
|
if cv2.waitKey(1) & 0xFF == 27:
|
|
|
|
|
break
|
2025-08-19 08:30:48 +08:00
|
|
|
|
|
|
|
|
|
except KeyboardInterrupt:
|
2025-08-19 18:15:56 +08:00
|
|
|
|
print("检测到退出信号,结束程序")
|
2025-08-19 08:30:48 +08:00
|
|
|
|
finally:
|
2025-08-19 18:15:56 +08:00
|
|
|
|
if self.device_handle:
|
|
|
|
|
self.device_handle.stop()
|
|
|
|
|
self.device_handle.close()
|
|
|
|
|
cv2.destroyAllWindows()
|
2025-08-19 08:30:48 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2025-08-19 18:15:56 +08:00
|
|
|
|
viewer = FemtoBoltViewer(depth_min=900, depth_max=1100)
|
2025-08-19 08:30:48 +08:00
|
|
|
|
viewer.run()
|