Azure Kinect双相机图像采集代码(纯Python)


一、前言

第一篇帖子中讲解了基于python语言的AzureKinect相机图像采集代码,主要是使用了官方SDK以及pyk4a库,详情可查看下方链接:
单Azure Kinect相机图像采集(python)


本文将继续深入,讲解如何通过改写pyk4a库,实现基于python语言的,双Azure Kinect图像实时采集,并且可实时显示两台相机的视频流

二、开发原理

1、pyk4a简介

pyk4a是一个开源项目,它为Azure Kinect DK提供了一个简单易用的Python接口。它构建在libk4a之上,这是Microsoft提供的官方C库。pyk4a允许用户轻松地访问摄像头数据并控制摄像头的各种功能。

安装

安装pyk4a可以通过pip来完成:

pip install pyk4a

示例代码

下面是一个简单的示例代码,展示如何使用pyk4a来捕获和显示RGB和深度图像:

from pyk4a import PyK4A
import cv2

# 创建PyK4A实例
k4a = PyK4A()

# 开始捕获
k4a.start()

# 打开RGB和深度图像窗口
cv2.namedWindow('Color Image', cv2.WINDOW_NORMAL)
cv2.namedWindow('Depth Image', cv2.WINDOW_NORMAL)

try:
    while True:
        # 获取捕获帧
        capture = k4a.get_capture()

        # 提取RGB和深度图像
        color_image = capture.color
        depth_image = capture.transformed_depth

        # 显示图像
        cv2.imshow('Color Image', color_image)
        cv2.imshow('Depth Image', depth_image)

        # 按下'q'键退出循环
        if cv2.waitKey(1) == ord('q'):
            break
finally:
    # 清理资源
    k4a.stop()
    cv2.destroyAllWindows()

注意事项

在使用pyk4a之前,请确保你的系统已经正确安装了Azure Kinect SDK

2、pyk4a库的改写

原始的pyk4a不支持两台相机同时获取视频流并进行实时拍照,因此针对原有库中内容进行改写,使其支持双相机操作。已经改好已通过本账号上传
pyk4a(双相机版)下载链接
原始pyk4a库的文件结构如下图:
pyk4a库文件

使用方法

修改后的pyk4a库使用时无需通过pip命令安装,直接放置于双相机图像采集代码的同一路径下,然后在采图代码中通过import命令引用相关类,即可。

改写内容

需要改写的文件如下图:
改写文件

1、init.py 文件的修改

在init文件中,主要修改的是引用部分

from .pyk4a1 import ColorControlCapabilities, PyK4A1
from .pyk4a1 import ColorControlCapabilities, PyK4A

其余部分大家可以对照官方的代码看看

from .calibration import Calibration, CalibrationType
from .capture import PyK4ACapture
from .capture import PyK4A1Capture
from .config import (
    FPS,
    ColorControlCommand,
    ColorControlMode,
    ColorResolution,
    Config,
    DepthMode,
    ImageFormat,
    WiredSyncMode,
)
from .errors import K4AException, K4ATimeoutException
from .module import k4a_module
from .playback import PyK4APlayback, SeekOrigin
from .pyk4a1 import ColorControlCapabilities, PyK4A1
from .pyk4a1 import ColorControlCapabilities, PyK4A
from .record import PyK4ARecord
from .record import PyK4A1Record
from .transformation import (
    color_image_to_depth_camera,
    depth_image_to_color_camera,
    depth_image_to_color_camera_custom,
    depth_image_to_point_cloud,
)


def connected_device_count() -> int:
    return k4a_module.device_get_installed_count()


__all__ = (
    "Calibration",
    "CalibrationType",
    "FPS",
    "ColorControlCommand",
    "ColorControlMode",
    "ImageFormat",
    "ColorResolution",
    "Config",
    "DepthMode",
    "WiredSyncMode",
    "K4AException",
    "K4ATimeoutException",
    "PyK4A",
    "PyK4ACapture",
    "PyK4A1Capture",
    "PyK4APlayback",
    "SeekOrigin",
    "ColorControlCapabilities",
    "color_image_to_depth_camera",
    "depth_image_to_point_cloud",
    "depth_image_to_color_camera",
    "depth_image_to_color_camera_custom",
    "PyK4ARecord",
    "PyK4A1Record",
    "connected_device_count",
)

2、capture.py文件的修改

capture文件修改是多相机采图的核心,主要是将PyK4ACapture类复制,重新生成一个新的PyK4A1Capture类,类似地通过这种方式可以实现三相机、四相机的图像采集(电脑CPU够用的话)

from typing import Optional
import numpy as np
from .calibration import Calibration
from .config import ImageFormat
from .errors import K4AException
from .module import k4a_module
from .transformation import (
    color_image_to_depth_camera,
    depth_image_to_color_camera,
    depth_image_to_color_camera_custom,
    depth_image_to_point_cloud,
)
class PyK4A1Capture:
    def __init__(
        self, calibration: Calibration, capture_handle: object, color_format: ImageFormat, thread_safe: bool = True
    ):
        self._calibration: Calibration = calibration
        self._capture_handle: object = capture_handle  # built-in PyCapsule
        self.thread_safe = thread_safe
        self._color_format = color_format

        self._color: Optional[np.ndarray] = None
        self._color_timestamp_usec: int = 0
        self._color_system_timestamp_nsec: int = 0
        self._color_exposure_usec: Optional[int] = None
        self._color_white_balance: Optional[int] = None
        self._depth: Optional[np.ndarray] = None
        self._depth_timestamp_usec: int = 0
        self._depth_system_timestamp_nsec: int = 0
        self._ir: Optional[np.ndarray] = None
        self._ir_timestamp_usec: int = 0
        self._ir_system_timestamp_nsec: int = 0
        self._depth_point_cloud: Optional[np.ndarray] = None
        self._transformed_depth: Optional[np.ndarray] = None
        self._transformed_depth_point_cloud: Optional[np.ndarray] = None
        self._transformed_color: Optional[np.ndarray] = None
        self._transformed_ir: Optional[np.ndarray] = None

    @property
    def color(self) -> Optional[np.ndarray]:
        if self._color is None:
            (
                self._color,
                self._color_timestamp_usec,
                self._color_system_timestamp_nsec,
            ) = k4a_module.capture_get_color_image(self._capture_handle, self.thread_safe)
        return self._color

    @property
    def color_timestamp_usec(self) -> int:
        """Device timestamp for color image. Not equal host machine timestamp!"""
        if self._color is None:
            self.color
        return self._color_timestamp_usec

    @property
    def color_system_timestamp_nsec(self) -> int:
        """System timestamp for color image in nanoseconds. Corresponds to Python's time.perf_counter_ns()."""
        if self._color is None:
            self.color
        return self._color_system_timestamp_nsec

    @property
    def color_exposure_usec(self) -> int:
        if self._color_exposure_usec is None:
            value = k4a_module.color_image_get_exposure_usec(self._capture_handle)
            if value == 0:
                raise K4AException("Cannot read exposure from color image")
            self._color_exposure_usec = value
        return self._color_exposure_usec

    @property
    def color_white_balance(self) -> int:
        if self._color_white_balance is None:
            value = k4a_module.color_image_get_white_balance(self._capture_handle)
            if value == 0:
                raise K4AException("Cannot read white balance from color image")
            self._color_white_balance = value
        return self._color_white_balance

    @property
    def depth(self) -> Optional[np.ndarray]:
        if self._depth is None:
            (
                self._depth,
                self._depth_timestamp_usec,
                self._depth_system_timestamp_nsec,
            ) = k4a_module.capture_get_depth_image(self._capture_handle, self.thread_safe)
        return self._depth

    @property
    def depth_timestamp_usec(self) -> int:
        """Device timestamp for depth image. Not equal host machine timestamp!. Like as equal IR image timestamp"""
        if self._depth is None:
            self.depth
        return self._depth_timestamp_usec

    @property
    def depth_system_timestamp_nsec(self) -> int:
        """System timestamp for depth image in nanoseconds. Corresponds to Python's time.perf_counter_ns()."""
        if self._depth is None:
            self.depth
        return self._depth_system_timestamp_nsec

    @property
    def ir(self) -> Optional[np.ndarray]:
        """Device timestamp for IR image. Not equal host machine timestamp!. Like as equal depth image timestamp"""
        if self._ir is None:
            self._ir, self._ir_timestamp_usec, self._ir_system_timestamp_nsec = k4a_module.capture_get_ir_image(
                self._capture_handle, self.thread_safe
            )
        return self._ir

    @property
    def ir_timestamp_usec(self) -> int:
        if self._ir is None:
            self.ir
        return self._ir_timestamp_usec

    @property
    def ir_system_timestamp_nsec(self) -> int:
        """System timestamp for IR image in nanoseconds. Corresponds to Python's time.perf_counter_ns()."""
        if self._ir is None:
            self.ir
        return self._ir_system_timestamp_nsec

    @property
    def transformed_depth(self) -> Optional[np.ndarray]:
        if self._transformed_depth is None and self.depth is not None:
            self._transformed_depth = depth_image_to_color_camera(self._depth, self._calibration, self.thread_safe)
        return self._transformed_depth

    @property
    def depth_point_cloud(self) -> Optional[np.ndarray]:
        if self._depth_point_cloud is None and self.depth is not None:
            self._depth_point_cloud = depth_image_to_point_cloud(
                self._depth,
                self._calibration,
                self.thread_safe,
                calibration_type_depth=True,
            )
        return self._depth_point_cloud

    @property
    def transformed_depth_point_cloud(self) -> Optional[np.ndarray]:
        if self._transformed_depth_point_cloud is None and self.transformed_depth is not None:
            self._transformed_depth_point_cloud = depth_image_to_point_cloud(
                self.transformed_depth,
                self._calibration,
                self.thread_safe,
                calibration_type_depth=False,
            )
        return self._transformed_depth_point_cloud

    @property
    def transformed_color(self) -> Optional[np.ndarray]:
        if self._transformed_color is None and self.depth is not None and self.color is not None:
            if self._color_format != ImageFormat.COLOR_BGRA32:
                raise RuntimeError(
                    "color color_image must be of color_format K4A_IMAGE_FORMAT_COLOR_BGRA32 for "
                    "transformation_color_image_to_depth_camera"
                )
            self._transformed_color = color_image_to_depth_camera(
                self.color, self.depth, self._calibration, self.thread_safe
            )
        return self._transformed_color

    @property
    def transformed_ir(self) -> Optional[np.ndarray]:
        if self._transformed_ir is None and self.ir is not None and self.depth is not None:
            result = depth_image_to_color_camera_custom(self.depth, self.ir, self._calibration, self.thread_safe)
            if result is None:
                return None
            else:
                self._transformed_ir, self._transformed_depth = result
        return self._transformed_ir

class PyK4ACapture:
    def __init__(
        self, calibration: Calibration, capture_handle: object, color_format: ImageFormat, thread_safe: bool = True
    ):
        self._calibration: Calibration = calibration
        self._capture_handle: object = capture_handle  # built-in PyCapsule
        self.thread_safe = thread_safe
        self._color_format = color_format

        self._color: Optional[np.ndarray] = None
        self._color_timestamp_usec: int = 0
        self._color_system_timestamp_nsec: int = 0
        self._color_exposure_usec: Optional[int] = None
        self._color_white_balance: Optional[int] = None
        self._depth: Optional[np.ndarray] = None
        self._depth_timestamp_usec: int = 0
        self._depth_system_timestamp_nsec: int = 0
        self._ir: Optional[np.ndarray] = None
        self._ir_timestamp_usec: int = 0
        self._ir_system_timestamp_nsec: int = 0
        self._depth_point_cloud: Optional[np.ndarray] = None
        self._transformed_depth: Optional[np.ndarray] = None
        self._transformed_depth_point_cloud: Optional[np.ndarray] = None
        self._transformed_color: Optional[np.ndarray] = None
        self._transformed_ir: Optional[np.ndarray] = None

    @property
    def color(self) -> Optional[np.ndarray]:
        if self._color is None:
            (
                self._color,
                self._color_timestamp_usec,
                self._color_system_timestamp_nsec,
            ) = k4a_module.capture_get_color_image(self._capture_handle, self.thread_safe)
        return self._color

    @property
    def color_timestamp_usec(self) -> int:
        """Device timestamp for color image. Not equal host machine timestamp!"""
        if self._color is None:
            self.color
        return self._color_timestamp_usec

    @property
    def color_system_timestamp_nsec(self) -> int:
        """System timestamp for color image in nanoseconds. Corresponds to Python's time.perf_counter_ns()."""
        if self._color is None:
            self.color
        return self._color_system_timestamp_nsec

    @property
    def color_exposure_usec(self) -> int:
        if self._color_exposure_usec is None:
            value = k4a_module.color_image_get_exposure_usec(self._capture_handle)
            if value == 0:
                raise K4AException("Cannot read exposure from color image")
            self._color_exposure_usec = value
        return self._color_exposure_usec

    @property
    def color_white_balance(self) -> int:
        if self._color_white_balance is None:
            value = k4a_module.color_image_get_white_balance(self._capture_handle)
            if value == 0:
                raise K4AException("Cannot read white balance from color image")
            self._color_white_balance = value
        return self._color_white_balance

    @property
    def depth(self) -> Optional[np.ndarray]:
        if self._depth is None:
            (
                self._depth,
                self._depth_timestamp_usec,
                self._depth_system_timestamp_nsec,
            ) = k4a_module.capture_get_depth_image(self._capture_handle, self.thread_safe)
        return self._depth

    @property
    def depth_timestamp_usec(self) -> int:
        """Device timestamp for depth image. Not equal host machine timestamp!. Like as equal IR image timestamp"""
        if self._depth is None:
            self.depth
        return self._depth_timestamp_usec

    @property
    def depth_system_timestamp_nsec(self) -> int:
        """System timestamp for depth image in nanoseconds. Corresponds to Python's time.perf_counter_ns()."""
        if self._depth is None:
            self.depth
        return self._depth_system_timestamp_nsec

    @property
    def ir(self) -> Optional[np.ndarray]:
        """Device timestamp for IR image. Not equal host machine timestamp!. Like as equal depth image timestamp"""
        if self._ir is None:
            self._ir, self._ir_timestamp_usec, self._ir_system_timestamp_nsec = k4a_module.capture_get_ir_image(
                self._capture_handle, self.thread_safe
            )
        return self._ir

    @property
    def ir_timestamp_usec(self) -> int:
        if self._ir is None:
            self.ir
        return self._ir_timestamp_usec

    @property
    def ir_system_timestamp_nsec(self) -> int:
        """System timestamp for IR image in nanoseconds. Corresponds to Python's time.perf_counter_ns()."""
        if self._ir is None:
            self.ir
        return self._ir_system_timestamp_nsec

    @property
    def transformed_depth(self) -> Optional[np.ndarray]:
        if self._transformed_depth is None and self.depth is not None:
            self._transformed_depth = depth_image_to_color_camera(self._depth, self._calibration, self.thread_safe)
        return self._transformed_depth

    @property
    def depth_point_cloud(self) -> Optional[np.ndarray]:
        if self._depth_point_cloud is None and self.depth is not None:
            self._depth_point_cloud = depth_image_to_point_cloud(
                self._depth,
                self._calibration,
                self.thread_safe,
                calibration_type_depth=True,
            )
        return self._depth_point_cloud

    @property
    def transformed_depth_point_cloud(self) -> Optional[np.ndarray]:
        if self._transformed_depth_point_cloud is None and self.transformed_depth is not None:
            self._transformed_depth_point_cloud = depth_image_to_point_cloud(
                self.transformed_depth,
                self._calibration,
                self.thread_safe,
                calibration_type_depth=False,
            )
        return self._transformed_depth_point_cloud

    @property
    def transformed_color(self) -> Optional[np.ndarray]:
        if self._transformed_color is None and self.depth is not None and self.color is not None:
            if self._color_format != ImageFormat.COLOR_BGRA32:
                raise RuntimeError(
                    "color color_image must be of color_format K4A_IMAGE_FORMAT_COLOR_BGRA32 for "
                    "transformation_color_image_to_depth_camera"
                )
            self._transformed_color = color_image_to_depth_camera(
                self.color, self.depth, self._calibration, self.thread_safe
            )
        return self._transformed_color

    @property
    def transformed_ir(self) -> Optional[np.ndarray]:
        if self._transformed_ir is None and self.ir is not None and self.depth is not None:
            result = depth_image_to_color_camera_custom(self.depth, self.ir, self._calibration, self.thread_safe)
            if result is None:
                return None
            else:
                self._transformed_ir, self._transformed_depth = result
        return self._transformed_ir
3、playback.py文件的修改

该文件主要是将代码第17行的

from .pyk4a import ImuSample

修改为

from .pyk4a1 import ImuSample
4、pyk4a.py文件的修改

首先要将pyk4a文件重新命名为pyk4a1.py,防止与自己环境中安装的官方pyk4a包产生冲突

import sys
from typing import Any, Optional, Tuple

from .calibration import Calibration
from .capture import PyK4ACapture
from .config import ColorControlCommand, ColorControlMode, Config
from .errors import K4AException, _verify_error
from .module import k4a_module


if sys.version_info < (3, 8):
    from typing_extensions import TypedDict
else:
    from typing import TypedDict


class PyK4A1:
    TIMEOUT_WAIT_INFINITE = -1

    def __init__(self, config: Optional[Config] = None, device_id: int = 0,thread_safe: bool = True):
        self._device_id = 0
        self._config: Config = config if (config is not None) else Config()
        self.thread_safe = thread_safe
        self._device_handle: Optional[object] = None
        # self._device_handle1: Optional[object] = None
        self._calibration: Optional[Calibration] = None
        self.is_running = False

    def start(self):
        """
        Open device if device not opened, then start cameras and IMU
        All-in-one function
        :return:
        """
        if not self.opened:
            self.open()
        self._start_cameras()
        self._start_imu()
        self.is_running = True

    def stop(self):
        """
        Stop cameras, IMU, ... and close device
        :return:
        """
        self._stop_imu()
        self._stop_cameras()
        self._device_close()
        self.is_running = False

    def __del__(self):
        if self.is_running:
            self.stop()
        elif self.opened:
            self.close()

    @property
    def opened(self) -> bool:
        return self._device_handle is not None

    def open(self):
        """
        Open device
        You must open device before querying any information
        """
        # if self.opened:
        #     raise K4AException("Device already opened")
        self._device_open()

    def close(self):
        self._validate_is_opened()
        self._device_close()

    def save_calibration_json(self, path: Any):
        with open(path, "w") as f:
            f.write(self.calibration_raw)

    def load_calibration_json(self, path: Any):
        with open(path, "r") as f:
            calibration = f.read()
        self.calibration_raw = calibration

    def _device_open(self):
        res, handle = k4a_module.device_open(self._device_id, self.thread_safe)

        self._device_handle = handle

        _verify_error(res)

    def _device_close(self):
        res = k4a_module.device_close(self._device_handle, self.thread_safe)
        _verify_error(res)
        self._device_handle = None

    def _start_cameras(self):
        res = k4a_module.device_start_cameras(self._device_handle, self.thread_safe, *self._config.unpack())
        _verify_error(res)

    def _start_imu(self):
        res = k4a_module.device_start_imu(self._device_handle, self.thread_safe)
        _verify_error(res)

    def _stop_cameras(self):
        res = k4a_module.device_stop_cameras(self._device_handle, self.thread_safe)
        _verify_error(res)

    def _stop_imu(self):
        res = k4a_module.device_stop_imu(self._device_handle, self.thread_safe)
        _verify_error(res)

    def get_capture1(
        self,
        timeout=TIMEOUT_WAIT_INFINITE,
    ) -> "PyK4ACapture":
        """
        Fetch a capture from the device and return a PyK4ACapture object. Images are
        lazily fetched.

        Arguments:
            :param timeout: Timeout in ms. Default is infinite.

        Returns:
            :return capture containing requested images and infos if they are available
                in the current capture. There are no guarantees that the returned
                object will contain all the requested images.

        If using any ImageFormat other than ImageFormat.COLOR_BGRA32, the color color_image must be
        decoded. See example/color_formats.py
        """
        self._validate_is_opened()
        res, capture_capsule = k4a_module.device_get_capture(self._device_handle, self.thread_safe, timeout)
        _verify_error(res)

        capture = PyK4ACapture(
            calibration=self.calibration,
            capture_handle=capture_capsule,
            color_format=self._config.color_format,
            thread_safe=self.thread_safe,
        )
        return capture


    def get_imu_sample(self, timeout: int = TIMEOUT_WAIT_INFINITE) -> Optional["ImuSample"]:
        self._validate_is_opened()
        res, imu_sample = k4a_module.device_get_imu_sample(self._device_handle, self.thread_safe, timeout)
        _verify_error(res)
        return imu_sample

    @property
    def serial(self) -> str:
        self._validate_is_opened()
        ret = k4a_module.device_get_serialnum(self._device_handle, self.thread_safe)
        if ret == "":
            raise K4AException("Cannot read serial")
        return ret

    @property
    def calibration_raw(self) -> str:
        self._validate_is_opened()
        raw = k4a_module.device_get_raw_calibration(self._device_handle, self.thread_safe)
        return raw

    @calibration_raw.setter
    def calibration_raw(self, value: str):
        self._validate_is_opened()
        self._calibration = Calibration.from_raw(
            value, self._config.depth_mode, self._config.color_resolution, self.thread_safe
        )

    @property
    def sync_jack_status(self) -> Tuple[bool, bool]:
        self._validate_is_opened()
        res, jack_in, jack_out = k4a_module.device_get_sync_jack(self._device_handle, self.thread_safe)
        _verify_error(res)
        return jack_in == 1, jack_out == 1

    def _get_color_control(self, cmd: ColorControlCommand) -> Tuple[int, ColorControlMode]:
        self._validate_is_opened()
        res, mode, value = k4a_module.device_get_color_control(self._device_handle, self.thread_safe, cmd)
        _verify_error(res)
        return value, ColorControlMode(mode)

    def _set_color_control(self, cmd: ColorControlCommand, value: int, mode=ColorControlMode.MANUAL):
        self._validate_is_opened()
        res = k4a_module.device_set_color_control(self._device_handle, self.thread_safe, cmd, mode, value)
        _verify_error(res)

    @property
    def brightness(self) -> int:
        return self._get_color_control(ColorControlCommand.BRIGHTNESS)[0]

    @brightness.setter
    def brightness(self, value: int):
        self._set_color_control(ColorControlCommand.BRIGHTNESS, value)

    @property
    def contrast(self) -> int:
        return self._get_color_control(ColorControlCommand.CONTRAST)[0]

    @contrast.setter
    def contrast(self, value: int):
        self._set_color_control(ColorControlCommand.CONTRAST, value)

    @property
    def saturation(self) -> int:
        return self._get_color_control(ColorControlCommand.SATURATION)[0]

    @saturation.setter
    def saturation(self, value: int):
        self._set_color_control(ColorControlCommand.SATURATION, value)

    @property
    def sharpness(self) -> int:
        return self._get_color_control(ColorControlCommand.SHARPNESS)[0]

    @sharpness.setter
    def sharpness(self, value: int):
        self._set_color_control(ColorControlCommand.SHARPNESS, value)

    @property
    def backlight_compensation(self) -> int:
        return self._get_color_control(ColorControlCommand.BACKLIGHT_COMPENSATION)[0]

    @backlight_compensation.setter
    def backlight_compensation(self, value: int):
        self._set_color_control(ColorControlCommand.BACKLIGHT_COMPENSATION, value)

    @property
    def gain(self) -> int:
        return self._get_color_control(ColorControlCommand.GAIN)[0]

    @gain.setter
    def gain(self, value: int):
        self._set_color_control(ColorControlCommand.GAIN, value)

    @property
    def powerline_frequency(self) -> int:
        return self._get_color_control(ColorControlCommand.POWERLINE_FREQUENCY)[0]

    @powerline_frequency.setter
    def powerline_frequency(self, value: int):
        self._set_color_control(ColorControlCommand.POWERLINE_FREQUENCY, value)

    @property
    def exposure(self) -> int:
        # sets mode to manual
        return self._get_color_control(ColorControlCommand.EXPOSURE_TIME_ABSOLUTE)[0]

    @exposure.setter
    def exposure(self, value: int):
        self._set_color_control(ColorControlCommand.EXPOSURE_TIME_ABSOLUTE, value)

    @property
    def exposure_mode_auto(self) -> bool:
        return self._get_color_control(ColorControlCommand.EXPOSURE_TIME_ABSOLUTE)[1] == ColorControlMode.AUTO

    @exposure_mode_auto.setter
    def exposure_mode_auto(self, mode_auto: bool, value: int = 2500):
        mode = ColorControlMode.AUTO if mode_auto else ColorControlMode.MANUAL
        self._set_color_control(ColorControlCommand.EXPOSURE_TIME_ABSOLUTE, value=value, mode=mode)

    @property
    def whitebalance(self) -> int:
        # sets mode to manual
        return self._get_color_control(ColorControlCommand.WHITEBALANCE)[0]

    @whitebalance.setter
    def whitebalance(self, value: int):
        self._set_color_control(ColorControlCommand.WHITEBALANCE, value)

    @property
    def whitebalance_mode_auto(self) -> bool:
        return self._get_color_control(ColorControlCommand.WHITEBALANCE)[1] == ColorControlMode.AUTO

    @whitebalance_mode_auto.setter
    def whitebalance_mode_auto(self, mode_auto: bool, value: int = 2500):
        mode = ColorControlMode.AUTO if mode_auto else ColorControlMode.MANUAL
        self._set_color_control(ColorControlCommand.WHITEBALANCE, value=value, mode=mode)

    def _get_color_control_capabilities(self, cmd: ColorControlCommand) -> Optional["ColorControlCapabilities"]:
        self._validate_is_opened()
        res, capabilities = k4a_module.device_get_color_control_capabilities(self._device_handle, self.thread_safe, cmd)
        _verify_error(res)
        return capabilities

    def reset_color_control_to_default(self):
        for cmd in ColorControlCommand:
            capability = self._get_color_control_capabilities(cmd)
            self._set_color_control(cmd, capability["default_value"], capability["default_mode"])

    @property
    def calibration(self) -> Calibration:
        self._validate_is_opened()
        if not self._calibration:
            res, calibration_handle = k4a_module.device_get_calibration(
                self._device_handle, self.thread_safe, self._config.depth_mode, self._config.color_resolution
            )
            _verify_error(res)
            self._calibration = Calibration(
                handle=calibration_handle,
                depth_mode=self._config.depth_mode,
                color_resolution=self._config.color_resolution,
                thread_safe=self.thread_safe,
            )
        return self._calibration

    def _validate_is_opened(self):
        if not self.opened:
            raise K4AException("Device is not opened")


class ImuSample(TypedDict):
    temperature: float
    acc_sample: Tuple[float, float, float]
    acc_timestamp: int
    gyro_sample: Tuple[float, float, float]
    gyro_timestamp: int


class ColorControlCapabilities(TypedDict):
    color_control_command: ColorControlCommand
    supports_auto: bool
    min_value: int
    max_value: int
    step_value: int
    default_value: int
    default_mode: ColorControlMode


class PyK4A:
    TIMEOUT_WAIT_INFINITE = -1

    def __init__(self, config: Optional[Config] = None, device_id: int = 1,thread_safe: bool = True):
        self._device_id = 1
        self._config: Config = config if (config is not None) else Config()
        self.thread_safe = thread_safe
        self.thread_safe1 = thread_safe
        self._device_handle: Optional[object] = None
        self._calibration: Optional[Calibration] = None
        self.is_running = False

    def start(self):
        """
        Open device if device not opened, then start cameras and IMU
        All-in-one function
        :return:
        """
        if not self.opened:
            self.open()
        self._start_cameras()
        self._start_imu()
        self.is_running = True

    def stop(self):
        """
        Stop cameras, IMU, ... and close device
        :return:
        """
        self._stop_imu()
        self._stop_cameras()
        self._device_close()
        self.is_running = False

    def __del__(self):
        if self.is_running:
            self.stop()
        elif self.opened:
            self.close()

    @property
    def opened(self) -> bool:
        return self._device_handle is not None

    def open(self):
        """
        Open device
        You must open device before querying any information
        """
        # if self.opened:
        #     raise K4AException("Device already opened")
        self._device_open()

    def close(self):
        self._validate_is_opened()
        self._device_close()

    def save_calibration_json(self, path: Any):
        with open(path, "w") as f:
            f.write(self.calibration_raw)

    def load_calibration_json(self, path: Any):
        with open(path, "r") as f:
            calibration = f.read()
        self.calibration_raw = calibration

    def _device_open(self):
        res, handle = k4a_module.device_open(self._device_id, self.thread_safe)

        self._device_handle = handle

        _verify_error(res)

    def _device_close(self):
        res = k4a_module.device_close(self._device_handle, self.thread_safe)
        _verify_error(res)
        self._device_handle = None

    def _start_cameras(self):
        res = k4a_module.device_start_cameras(self._device_handle, self.thread_safe, *self._config.unpack())
        _verify_error(res)

    def _start_imu(self):
        res = k4a_module.device_start_imu(self._device_handle, self.thread_safe)
        _verify_error(res)

    def _stop_cameras(self):
        res = k4a_module.device_stop_cameras(self._device_handle, self.thread_safe)
        _verify_error(res)

    def _stop_imu(self):
        res = k4a_module.device_stop_imu(self._device_handle, self.thread_safe)
        _verify_error(res)

    def get_capture(
        self,
        timeout=TIMEOUT_WAIT_INFINITE,
    ) -> "PyK4A1Capture":
        """
        Fetch a capture from the device and return a PyK4ACapture object. Images are
        lazily fetched.

        Arguments:
            :param timeout: Timeout in ms. Default is infinite.

        Returns:
            :return capture containing requested images and infos if they are available
                in the current capture. There are no guarantees that the returned
                object will contain all the requested images.

        If using any ImageFormat other than ImageFormat.COLOR_BGRA32, the color color_image must be
        decoded. See example/color_formats.py
        """
        self._validate_is_opened()
        res, capture_capsule = k4a_module.device_get_capture(self._device_handle, self.thread_safe1, timeout)
        _verify_error(res)

        capture = PyK4ACapture(
            calibration=self.calibration,
            capture_handle=capture_capsule,
            color_format=self._config.color_format,
            thread_safe=self.thread_safe,
        )
        return capture

    def get_imu_sample(self, timeout: int = TIMEOUT_WAIT_INFINITE) -> Optional["ImuSample"]:
        self._validate_is_opened()
        res, imu_sample = k4a_module.device_get_imu_sample(self._device_handle, self.thread_safe, timeout)
        _verify_error(res)
        return imu_sample

    @property
    def serial(self) -> str:
        self._validate_is_opened()
        ret = k4a_module.device_get_serialnum(self._device_handle, self.thread_safe)
        if ret == "":
            raise K4AException("Cannot read serial")
        return ret

    @property
    def calibration_raw(self) -> str:
        self._validate_is_opened()
        raw = k4a_module.device_get_raw_calibration(self._device_handle, self.thread_safe)
        return raw

    @calibration_raw.setter
    def calibration_raw(self, value: str):
        self._validate_is_opened()
        self._calibration = Calibration.from_raw(
            value, self._config.depth_mode, self._config.color_resolution, self.thread_safe
        )

    @property
    def sync_jack_status(self) -> Tuple[bool, bool]:
        self._validate_is_opened()
        res, jack_in, jack_out = k4a_module.device_get_sync_jack(self._device_handle, self.thread_safe)
        _verify_error(res)
        return jack_in == 1, jack_out == 1

    def _get_color_control(self, cmd: ColorControlCommand) -> Tuple[int, ColorControlMode]:
        self._validate_is_opened()
        res, mode, value = k4a_module.device_get_color_control(self._device_handle, self.thread_safe, cmd)
        _verify_error(res)
        return value, ColorControlMode(mode)

    def _set_color_control(self, cmd: ColorControlCommand, value: int, mode=ColorControlMode.MANUAL):
        self._validate_is_opened()
        res = k4a_module.device_set_color_control(self._device_handle, self.thread_safe, cmd, mode, value)
        _verify_error(res)

    @property
    def brightness(self) -> int:
        return self._get_color_control(ColorControlCommand.BRIGHTNESS)[0]

    @brightness.setter
    def brightness(self, value: int):
        self._set_color_control(ColorControlCommand.BRIGHTNESS, value)

    @property
    def contrast(self) -> int:
        return self._get_color_control(ColorControlCommand.CONTRAST)[0]

    @contrast.setter
    def contrast(self, value: int):
        self._set_color_control(ColorControlCommand.CONTRAST, value)

    @property
    def saturation(self) -> int:
        return self._get_color_control(ColorControlCommand.SATURATION)[0]

    @saturation.setter
    def saturation(self, value: int):
        self._set_color_control(ColorControlCommand.SATURATION, value)

    @property
    def sharpness(self) -> int:
        return self._get_color_control(ColorControlCommand.SHARPNESS)[0]

    @sharpness.setter
    def sharpness(self, value: int):
        self._set_color_control(ColorControlCommand.SHARPNESS, value)

    @property
    def backlight_compensation(self) -> int:
        return self._get_color_control(ColorControlCommand.BACKLIGHT_COMPENSATION)[0]

    @backlight_compensation.setter
    def backlight_compensation(self, value: int):
        self._set_color_control(ColorControlCommand.BACKLIGHT_COMPENSATION, value)

    @property
    def gain(self) -> int:
        return self._get_color_control(ColorControlCommand.GAIN)[0]

    @gain.setter
    def gain(self, value: int):
        self._set_color_control(ColorControlCommand.GAIN, value)

    @property
    def powerline_frequency(self) -> int:
        return self._get_color_control(ColorControlCommand.POWERLINE_FREQUENCY)[0]

    @powerline_frequency.setter
    def powerline_frequency(self, value: int):
        self._set_color_control(ColorControlCommand.POWERLINE_FREQUENCY, value)

    @property
    def exposure(self) -> int:
        # sets mode to manual
        return self._get_color_control(ColorControlCommand.EXPOSURE_TIME_ABSOLUTE)[0]

    @exposure.setter
    def exposure(self, value: int):
        self._set_color_control(ColorControlCommand.EXPOSURE_TIME_ABSOLUTE, value)

    @property
    def exposure_mode_auto(self) -> bool:
        return self._get_color_control(ColorControlCommand.EXPOSURE_TIME_ABSOLUTE)[1] == ColorControlMode.AUTO

    @exposure_mode_auto.setter
    def exposure_mode_auto(self, mode_auto: bool, value: int = 2500):
        mode = ColorControlMode.AUTO if mode_auto else ColorControlMode.MANUAL
        self._set_color_control(ColorControlCommand.EXPOSURE_TIME_ABSOLUTE, value=value, mode=mode)

    @property
    def whitebalance(self) -> int:
        # sets mode to manual
        return self._get_color_control(ColorControlCommand.WHITEBALANCE)[0]

    @whitebalance.setter
    def whitebalance(self, value: int):
        self._set_color_control(ColorControlCommand.WHITEBALANCE, value)

    @property
    def whitebalance_mode_auto(self) -> bool:
        return self._get_color_control(ColorControlCommand.WHITEBALANCE)[1] == ColorControlMode.AUTO

    @whitebalance_mode_auto.setter
    def whitebalance_mode_auto(self, mode_auto: bool, value: int = 2500):
        mode = ColorControlMode.AUTO if mode_auto else ColorControlMode.MANUAL
        self._set_color_control(ColorControlCommand.WHITEBALANCE, value=value, mode=mode)

    def _get_color_control_capabilities(self, cmd: ColorControlCommand) -> Optional["ColorControlCapabilities"]:
        self._validate_is_opened()
        res, capabilities = k4a_module.device_get_color_control_capabilities(self._device_handle, self.thread_safe, cmd)
        _verify_error(res)
        return capabilities

    def reset_color_control_to_default(self):
        for cmd in ColorControlCommand:
            capability = self._get_color_control_capabilities(cmd)
            self._set_color_control(cmd, capability["default_value"], capability["default_mode"])

    @property
    def calibration(self) -> Calibration:
        self._validate_is_opened()
        if not self._calibration:
            res, calibration_handle = k4a_module.device_get_calibration(
                self._device_handle, self.thread_safe, self._config.depth_mode, self._config.color_resolution
            )
            _verify_error(res)
            self._calibration = Calibration(
                handle=calibration_handle,
                depth_mode=self._config.depth_mode,
                color_resolution=self._config.color_resolution,
                thread_safe=self.thread_safe,
            )
        return self._calibration

    def _validate_is_opened(self):
        if not self.opened:
            raise K4AException("Device is not opened")


class ImuSample(TypedDict):
    temperature: float
    acc_sample: Tuple[float, float, float]
    acc_timestamp: int
    gyro_sample: Tuple[float, float, float]
    gyro_timestamp: int


class ColorControlCapabilities(TypedDict):
    color_control_command: ColorControlCommand
    supports_auto: bool
    min_value: int
    max_value: int
    step_value: int
    default_value: int
    default_mode: ColorControlMode

5、record.py文件的修改
from pathlib import Path
from typing import Optional, Union

from .capture import PyK4ACapture
from .capture import PyK4A1Capture
from .config import Config
from .errors import K4AException
from .module import k4a_module
from .pyk4a1 import PyK4A1
from .pyk4a1 import PyK4A
from .results import Result


class PyK4A1Record:
    def __init__(
        self, path: Union[str, Path], config: Config, device: Optional[PyK4A1] = None, thread_safe: bool = True
    ):
        self._path: Path = Path(path)
        self.thread_safe = thread_safe
        self._device: Optional[PyK4A1] = device
        self._config: Config = config
        self._handle: Optional[object] = None
        self._header_written: bool = False
        self._captures_count: int = 0

    def __del__(self):
        if self.created:
            self.close()

    def create(self) -> None:
        """Create record file"""
        if self.created:
            raise K4AException(f"Record already created {self._path}")
        device_handle = self._device._device_handle if self._device else None
        result, handle = k4a_module.record_create(
            device_handle, str(self._path), self.thread_safe, *self._config.unpack()
        )
        if result != Result.Success:
            raise K4AException(f"Cannot create record {self._path}")
        self._handle = handle

    def close(self):
        """Close record"""
        self._validate_is_created()
        k4a_module.record_close(self._handle, self.thread_safe)
        self._handle = None

    def write_header(self):
        """Write MKV header"""
        self._validate_is_created()
        if self.header_written:
            raise K4AException(f"Header already written {self._path}")
        result: Result = k4a_module.record_write_header(self._handle, self.thread_safe)
        if result != Result.Success:
            raise K4AException(f"Cannot write record header {self._path}")
        self._header_written = True

    def write_capture(self, capture: PyK4A1Capture):
        """Write capture to file (send to queue)"""
        self._validate_is_created()
        if not self.header_written:
            self.write_header()
        result: Result = k4a_module.record_write_capture(self._handle, capture._capture_handle, self.thread_safe)
        if result != Result.Success:
            raise K4AException(f"Cannot write capture {self._path}")
        self._captures_count += 1

    def flush(self):
        """Flush queue"""
        self._validate_is_created()
        result: Result = k4a_module.record_flush(self._handle, self.thread_safe)
        if result != Result.Success:
            raise K4AException(f"Cannot flush data {self._path}")

    @property
    def created(self) -> bool:
        return self._handle is not None

    @property
    def header_written(self) -> bool:
        return self._header_written

    @property
    def captures_count(self) -> int:
        return self._captures_count

    @property
    def path(self) -> Path:
        return self._path

    def _validate_is_created(self):
        if not self.created:
            raise K4AException("Record not created.")



class PyK4ARecord:
    def __init__(
        self, path: Union[str, Path], config: Config, device: Optional[PyK4A] = None, thread_safe: bool = True
    ):
        self._path: Path = Path(path)
        self.thread_safe = thread_safe
        self._device: Optional[PyK4A] = device
        self._config: Config = config
        self._handle: Optional[object] = None
        self._header_written: bool = False
        self._captures_count: int = 0

    def __del__(self):
        if self.created:
            self.close()

    def create(self) -> None:
        """Create record file"""
        if self.created:
            raise K4AException(f"Record already created {self._path}")
        device_handle = self._device._device_handle if self._device else None
        result, handle = k4a_module.record_create(
            device_handle, str(self._path), self.thread_safe, *self._config.unpack()
        )
        if result != Result.Success:
            raise K4AException(f"Cannot create record {self._path}")
        self._handle = handle

    def close(self):
        """Close record"""
        self._validate_is_created()
        k4a_module.record_close(self._handle, self.thread_safe)
        self._handle = None

    def write_header(self):
        """Write MKV header"""
        self._validate_is_created()
        if self.header_written:
            raise K4AException(f"Header already written {self._path}")
        result: Result = k4a_module.record_write_header(self._handle, self.thread_safe)
        if result != Result.Success:
            raise K4AException(f"Cannot write record header {self._path}")
        self._header_written = True

    def write_capture(self, capture: PyK4ACapture):
        """Write capture to file (send to queue)"""
        self._validate_is_created()
        if not self.header_written:
            self.write_header()
        result: Result = k4a_module.record_write_capture(self._handle, capture._capture_handle, self.thread_safe)
        if result != Result.Success:
            raise K4AException(f"Cannot write capture {self._path}")
        self._captures_count += 1

    def flush(self):
        """Flush queue"""
        self._validate_is_created()
        result: Result = k4a_module.record_flush(self._handle, self.thread_safe)
        if result != Result.Success:
            raise K4AException(f"Cannot flush data {self._path}")

    @property
    def created(self) -> bool:
        return self._handle is not None

    @property
    def header_written(self) -> bool:
        return self._header_written

    @property
    def captures_count(self) -> int:
        return self._captures_count

    @property
    def path(self) -> Path:
        return self._path

    def _validate_is_created(self):
        if not self.created:
            raise K4AException("Record not created.")

二、双相机图像采集代码

1).文件结构

项目文件结构如下,将修改后的pyk4a库与图像采集代码放在同一路径下
在这里插入图片描述

2).图像采集代码源码

代码引用情况如下:

import cv2
import pyk4a
import os
import numpy as np
from pyk4a import Config, PyK4A1

图像采集之前,需在代码中对两相机进行设置,如下:

   k4a1 = PyK4A(
        Config(
            color_resolution=pyk4a.ColorResolution.RES_3072P,   
            depth_mode=pyk4a.DepthMode.WFOV_UNBINNED,
            camera_fps =pyk4a.FPS.FPS_15          
        )
    )
    k4a1.start()
    capture = k4a1.get_capture()
    k4a2 = PyK4A1(
        Config(
            
            color_resolution=pyk4a.ColorResolution.RES_3072P,   
            depth_mode=pyk4a.DepthMode.WFOV_UNBINNED,
            camera_fps =pyk4a.FPS.FPS_15          
        )
    )
    k4a2.start()
    capture2 = k4a2.get_capture1()

生成capture1、capture2两个类,用于捕获两个相机的不同视频流。

图像采集代码源码如下:

import cv2
import pyk4a
import os
import numpy as np
from pyk4a import Config, PyK4A1



def main():
    k4a1 = PyK4A(
        Config(
            color_resolution=pyk4a.ColorResolution.RES_3072P,   
            depth_mode=pyk4a.DepthMode.WFOV_UNBINNED,
            camera_fps =pyk4a.FPS.FPS_15          
        )
    )
    k4a1.start()
    capture = k4a1.get_capture()
    k4a2 = PyK4A1(
        Config(
            
            color_resolution=pyk4a.ColorResolution.RES_3072P,   
            depth_mode=pyk4a.DepthMode.WFOV_UNBINNED,
            camera_fps =pyk4a.FPS.FPS_15          
        )
    )
    k4a2.start()
    capture2 = k4a2.get_capture1()
    count = 0   # 累加计数
    while True:
        Kinect_folder_path =  r"C:\Users\22898\Desktop\1" 
        capture = k4a1.get_capture()
        capture2 = k4a2.get_capture1()
        cv2.namedWindow('Kinect-color1', cv2.WINDOW_AUTOSIZE)
        cv2.imshow('Kinect-color1', cv2.resize(capture.color, ( 1024, 728)))
        cv2.namedWindow('Kinect-color2', cv2.WINDOW_AUTOSIZE)
        cv2.imshow('Kinect-color2', cv2.resize(capture2.color, ( 1024, 728)))
        key = cv2.waitKey(1)
        if key == ord('5'):
            count += 1
            #-----------Kinect图像保存---------------#
            Kinect_rgb_filename = f"{Kinect_folder_path}/1/rgb-{count}.png"
            cv2.imwrite(Kinect_rgb_filename , capture.color)
            print(f"基于Kinect1的 rgb-{count}.png  已保存!")
            
            Kinect_depth_filename = f"{Kinect_folder_path}/1/depth-{count}.png"
            cv2.imwrite(Kinect_depth_filename , capture.depth)
            print(f"基于Kinect1的 depth-{count}.png  已保存!")
            
            Kinect_ir_filename = f"{Kinect_folder_path}/1/ir-{count}.png"
            cv2.imwrite(Kinect_ir_filename , capture.ir)
            print(f"基于Kinect1的 ir-{count}.png  已保存!")
            
            Kinect_morp_filename = f"{Kinect_folder_path}/1/morp-{count}.png"
            cv2.imwrite(Kinect_morp_filename , capture.transformed_depth)
            print(f"基于Kinect1的 morp-{count}.png  已保存!")    
            #-----------Kinect图像保存---------------#
            Kinect_rgb_filename = f"{Kinect_folder_path}/2/rgb-{count}.png"
            cv2.imwrite(Kinect_rgb_filename , capture2.color)
            print(f"基于Kinect2的 rgb-{count}.png  已保存!")
            
            Kinect_depth_filename = f"{Kinect_folder_path}/2/depth-{count}.png"
            cv2.imwrite(Kinect_depth_filename , capture2.depth)
            print(f"基于Kinect2的 depth-{count}.png  已保存!")
            
            Kinect_ir_filename = f"{Kinect_folder_path}/2/ir-{count}.png"
            cv2.imwrite(Kinect_ir_filename , capture2.ir)
            print(f"基于Kinect2的 ir-{count}.png  已保存!")
            
            Kinect_morp_filename = f"{Kinect_folder_path}/2/morp-{count}.png"
            cv2.imwrite(Kinect_morp_filename , capture2.transformed_depth)
            print(f"基于Kinect2的 morp-{count}.png  已保存!")    
        if key == ord('1'):
            k4a1.stop()  
            k4a2.stop()  
            break
if __name__ == "__main__":
    main()

代码运行效果如下:
按“1”退出、按“5”采图
图像采集示意图

总结

本文讲述了一种通过修改pyk4a库来实现多相机图像采集的方法,并在最后提供了图像采集代码。但有一些改进空间、以及一些提示与大家分享

1、在进行图像采集时,两台相机占用CPU较高,经测,一般商务笔记本是难以带动的。个人猜测可以通过多线程修改视频流采样率等方式,降低双相机图像采集的算力要求,如果真有朋友在这方面进行研究,可以一起尝试一下。

2、在进行图像采集时,代码运行流畅度与图像数据写入的速度有很大关系,若代码中的图像存储路径为机械硬盘空间,则图像存储速率会收到较影响,有采图实时性要求的朋友可以注意一下这一点。

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值