cmvr-es-cli/cmvr/camera_client.py
2025-10-21 09:33:47 +08:00

443 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import grpc
import numpy as np
from PIL import Image
from typing import Tuple
from .enums import CMVRErrorCode
from .models import CameraState
from .models import CameraIntrinsics
class CameraClient:
"""相机客户端"""
def __init__(self, device_id: str, stub):
self.device_id = device_id
self.stub = stub
self.generated = None # 推迟导入
def _import_generated(self):
"""推迟导入 generated"""
if self.generated is None:
try:
# 从正确的路径导入生成的模块
from generated.cmvr.api import common_pb2, camera_command_pb2
self.generated = type('GeneratedModules', (), {
'common_pb2': common_pb2,
'camera_command_pb2': camera_command_pb2
})
except ImportError as e:
print(f"导入生成的模块失败: {e}")
print("请确保已生成 protobuf 代码")
raise
return self.generated
def _create_command_header(self):
generated = self._import_generated()
"""创建命令头"""
header = generated.common_pb2.CommandHeader.Request()
header.device_id = self.device_id
header.timestamp.GetCurrentTime()
return header
def get_status(self) -> Tuple[CMVRErrorCode, CameraState]:
"""获取相机状态"""
try:
generated = self._import_generated()
request = generated.camera_command_pb2.GetCameraStateCommand.Request()
request.header.CopyFrom(self._create_command_header())
response = self.stub.GetStatus(request)
if not response.header.success:
return CMVRErrorCode.CMVR_RPC_FAILED, CameraState()
state = CameraState(
is_initialized=response.state.is_initialized,
is_opened=response.state.is_opened,
is_streaming=response.state.is_streaming,
is_recording=response.state.is_recording,
width=response.state.width,
height=response.state.height,
fps=response.state.fps
)
return CMVRErrorCode.CMVR_SUCCESS, state
except grpc.RpcError as e:
print(f"获取相机状态失败: {e}")
return CMVRErrorCode.CMVR_RPC_FAILED, CameraState()
def start_camera(self) -> CMVRErrorCode:
"""启动相机"""
try:
generated = self._import_generated()
request = generated.camera_command_pb2.StartCameraCommand.Request()
request.header.CopyFrom(self._create_command_header())
response = self.stub.StartCamera(request)
return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED
except grpc.RpcError as e:
print(f"启动相机失败: {e}")
return CMVRErrorCode.CMVR_RPC_FAILED
def stop_camera(self) -> CMVRErrorCode:
"""停止相机"""
try:
generated = self._import_generated()
request = generated.camera_command_pb2.StopCameraCommand.Request()
request.header.CopyFrom(self._create_command_header())
response = self.stub.StopCamera(request)
return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED
except grpc.RpcError as e:
print(f"停止相机失败: {e}")
return CMVRErrorCode.CMVR_RPC_FAILED
def get_rgb_image(self) -> Tuple[CMVRErrorCode, np.ndarray, int, int]:
"""获取RGB图像"""
try:
generated = self._import_generated()
request = generated.camera_command_pb2.GetRGBImageCommand.Request()
request.header.CopyFrom(self._create_command_header())
response = self.stub.GetRGBImage(request)
if not response.header.success:
return CMVRErrorCode.CMVR_RPC_FAILED, np.array([]), 0, 0
width = response.color_frame.width
height = response.color_frame.height
size = len(response.color_frame.data)
# 将字节数据转换为numpy数组
img_data = np.frombuffer(response.color_frame.data, dtype=np.uint8)
# 从 Protobuf 消息中读取 intrinsics 数据
protobuf_intrinsics = response.intrinsics
# 构造 Python 数据类对象(字段名称完全对应)
python_intrinsics = CameraIntrinsics(
cx=protobuf_intrinsics.cx,
cy=protobuf_intrinsics.cy,
fx=protobuf_intrinsics.fx,
fy=protobuf_intrinsics.fy,
coeffs=list(protobuf_intrinsics.coeffs) # Protobuf repeated 字段通常是列表类型,直接转换
)
# 示例:访问读取后的数据
print(f"主点坐标: ({python_intrinsics.cx}, {python_intrinsics.cy})")
print(f"焦距: ({python_intrinsics.fx}, {python_intrinsics.fy})")
print(f"畸变系数: {python_intrinsics.coeffs}")
# 重塑为图像格式 (height, width, channels)
channels = 3 # 假设是RGB图像
if size == width * height * channels:
img_array = img_data.reshape((height, width, channels))
else:
# 可能是灰度图或其他格式
img_array = img_data.reshape((height, width))
return CMVRErrorCode.CMVR_SUCCESS, img_array, width, height
except grpc.RpcError as e:
print(f"获取图像失败: {e}")
return CMVRErrorCode.CMVR_RPC_FAILED, np.array([]), 0, 0
def get_depth_image(self) -> Tuple[CMVRErrorCode, np.ndarray, int, int, CameraIntrinsics]:
"""
获取深度图像及内参(修复返回值类型)
"""
# 创建默认内参实例(空值)用于错误返回
default_intrinsics = CameraIntrinsics(cx=0, cy=0, fx=0, fy=0, coeffs=[])
# 错误返回模板(类型与声明完全匹配)
error_return = (
CMVRErrorCode.CMVR_RPC_FAILED,
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
try:
generated = self._import_generated()
if not generated:
print("无法导入生成的protobuf模块")
return error_return
# 创建请求
request = generated.camera_command_pb2.GetDepthImageComand.Request()
request.header.CopyFrom(self._create_command_header())
# 调用RPC
response = self.stub.GetDepthImage(request)
if not response.header.success:
print(f"深度图像请求失败,错误码: {response.header.error_code}")
return (
CMVRErrorCode.CMVR_RPC_FAILED,
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
# 提取图像信息
width = response.depth_frame.width
height = response.depth_frame.height
depth_data = response.depth_frame.data
expected_size = width * height * 2
if len(depth_data) != expected_size:
print(f"深度图像数据尺寸不匹配: 预期 {expected_size}, 实际 {len(depth_data)}")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
# 转换为数组
depth_array = np.frombuffer(depth_data, dtype=np.uint16).reshape((height, width))
# 解析内参(确保始终返回有效实例)
intrinsics = CameraIntrinsics(
cx=response.intrinsics.cx,
cy=response.intrinsics.cy,
fx=response.intrinsics.fx,
fy=response.intrinsics.fy,
coeffs=list(response.intrinsics.coeffs) if hasattr(response.intrinsics, 'coeffs') else []
)
return (
CMVRErrorCode.CMVR_SUCCESS,
depth_array,
width,
height,
intrinsics
)
except grpc.RpcError as e:
print(f"深度图像RPC调用失败: {str(e)}")
return error_return
except Exception as e:
print(f"深度图像处理失败: {str(e)}")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
def get_rgbd_images(self) -> Tuple[CMVRErrorCode, np.ndarray, np.ndarray, int, int, CameraIntrinsics]:
"""
获取同步RGBD图像及内参修复返回值类型
"""
# 创建默认内参实例
default_intrinsics = CameraIntrinsics(cx=0, cy=0, fx=0, fy=0, coeffs=[])
# 错误返回模板
error_return = (
CMVRErrorCode.CMVR_RPC_FAILED,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
try:
generated = self._import_generated()
if not generated:
print("无法导入生成的protobuf模块")
return error_return
# 创建请求
request = generated.camera_command_pb2.GetRGBDImagesCommand.Request()
request.header.CopyFrom(self._create_command_header())
# 调用RPC
response = self.stub.GetRGBDImages(request)
if not response.header.success:
print(f"RGBD图像请求失败错误码: {response.header.error_code}")
return (
CMVRErrorCode.CMVR_RPC_FAILED,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
# 提取尺寸信息
rgb_width, rgb_height = response.color_frame.width, response.color_frame.height
depth_width, depth_height = response.depth_frame.width, response.depth_frame.height
if rgb_width != depth_width or rgb_height != depth_height:
print(f"RGBD尺寸不匹配: RGB({rgb_width}x{rgb_height}), Depth({depth_width}x{depth_height})")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
# 处理RGB图像
rgb_data = response.color_frame.data
rgb_expected = rgb_width * rgb_height * 3
if len(rgb_data) != rgb_expected:
print(f"RGB数据尺寸不匹配: 预期 {rgb_expected}, 实际 {len(rgb_data)}")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
rgb_array = np.frombuffer(rgb_data, dtype=np.uint8).reshape((rgb_height, rgb_width, 3))
# 处理深度图像
depth_data = response.depth_frame.data
depth_expected = depth_width * depth_height * 2
if len(depth_data) != depth_expected:
print(f"深度数据尺寸不匹配: 预期 {depth_expected}, 实际 {len(depth_data)}")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
depth_array = np.frombuffer(depth_data, dtype=np.uint16).reshape((depth_height, depth_width))
# 解析内参
intrinsics = CameraIntrinsics(
cx=response.intrinsics.cx,
cy=response.intrinsics.cy,
fx=response.intrinsics.fx,
fy=response.intrinsics.fy,
coeffs=list(response.intrinsics.coeffs) if hasattr(response.intrinsics, 'coeffs') else []
)
return (
CMVRErrorCode.CMVR_SUCCESS,
rgb_array,
depth_array,
rgb_width,
rgb_height,
intrinsics
)
except grpc.RpcError as e:
print(f"RGBD RPC调用失败: {str(e)}")
return error_return
except Exception as e:
print(f"RGBD图像处理失败: {str(e)}")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
def start_record(self, video_path: str) -> CMVRErrorCode:
"""开始录像"""
try:
generated = self._import_generated()
request = generated.camera_command_pb2.StartCameraRecordingCommand.Request()
request.header.CopyFrom(self._create_command_header())
request.video_path = video_path
response = self.stub.StartRecording(request)
return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED
except grpc.RpcError as e:
print(f"开始录像失败: {e}")
return CMVRErrorCode.CMVR_RPC_FAILED
def stop_record(self) -> CMVRErrorCode:
"""停止录像"""
try:
generated = self._import_generated()
request = generated.camera_command_pb2.StopCameraRecordingCommand.Request()
request.header.CopyFrom(self._create_command_header())
response = self.stub.StopRecording(request)
return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED
except grpc.RpcError as e:
print(f"停止录像失败: {e}")
return CMVRErrorCode.CMVR_RPC_FAILED
def create_rgb_stream_request(self):
"""创建流请求(无参数)"""
generated = self._import_generated()
request = generated.camera_command_pb2.GetRGBImageStreamCommand.Request()
request.header.CopyFrom(self._create_command_header())
# 不需要设置其他参数
return request
def get_rgb_stream(self, request_generator):
"""
双向流获取传感器数据
参数:
request_generator: 请求生成器
返回:
传感器数据流迭代器
"""
generated = self._import_generated()
return self.stub.GetRGBImageStream(request_generator)
def create_depth_stream_request(self):
"""创建流请求(无参数)"""
generated = self._import_generated()
request = generated.camera_command_pb2.GetDepthImageStreamCommand.Request()
request.header.CopyFrom(self._create_command_header())
# 不需要设置其他参数
return request
def get_depth_stream(self, request_generator):
"""
双向流获取传感器数据
参数:
request_generator: 请求生成器
返回:
传感器数据流迭代器
"""
generated = self._import_generated()
return self.stub.GetDepthImageStream(request_generator)
def create_rgbd_stream_request(self):
"""创建流请求(无参数)"""
generated = self._import_generated()
request = generated.camera_command_pb2.GetRGBDImageStreamCommand.Request()
request.header.CopyFrom(self._create_command_header())
# 不需要设置其他参数
return request
def get_rgbd_stream(self, request_generator):
"""
双向流获取传感器数据
参数:
request_generator: 请求生成器
返回:
传感器数据流迭代器
"""
generated = self._import_generated()
return self.stub.GetRGBDImageStream(request_generator)