HoloLens2-Unity-ResearchModeStreamer-master通过主机获取彩色和深度数据流

开发环境

Windows10专业版(开发者模式已打开)
Hololens2(开发者模式已打开,研究者模式已打开)
Visual Studio 2019
Unity2019.4.36.f1c1
Windows 10 SDK 10.0.18362.0
Python3.9(opencv-python)

1. 编译HoloLens2-Unity-ResearchModeStreamer

1.1 下载Github工程

HoloLens2-Unity-ResearchModeStreamer
该项目打开后,先阅读README.md文件,本实验是基于此文件操作的

1.2 编译HL2RmStreamUnityPlugin.dll

1)用VS打开下载好的项目里的HL2RmStreamUnityPlugin.sln文件
在这里插入图片描述
2)将项目属性设为ReleaseARM64
在这里插入图片描述
3)注释工程项目中Eigen库
在这里插入图片描述

4)生成解决方案
在这里插入图片描述
编译若出现E1696错误提示,解决方法可参考文章后面的常见错误

输出是dll,输出位置在

Your Location\HoloLens2-Unity-ResearchModeStreamer\HL2RmStreamUnityPlugin\ARM64\Release\HL2RmStreamUnityPlugin\

2. 配置Unity项目

2.1 Unity的3D项目

可以新建项目,也可以使用下载好的项目里的UnityHL2RmStreamer项目

2.1.1 新建Unity的3D项目

新建项目后,将项目切换到Universal windows patform平台
新建项目可以参考我之前的博文:Holoens2开发环境配置及项目程序部署里的2.1新建项目

或者直接使用下载好的项目里的UnityHL2RmStreamer项目

2.1.2 使用UnityHL2RmStreamer项目

若直接使用下载好的项目里的UnityHL2RmStreamer项目
1)先删除原项目里Assets文件夹下的的Scripts和Plugins文件夹

Your Location\HoloLens2-Unity-ResearchModeStreamer-master\UnityHL2RmStreamer\Assets

2)用Unity Hub打开该项目,再重新创建证书
点击工具栏FileBuild SettingsPlayer SettingsPlayer
在这里插入图片描述

2.2 新建文件路径

在unity项目里的Assets文件夹下新建两个分别命名为ScriptsARM64的文件夹
路径分别为

Assets/Scripts
Assets/Plugins/WSAPlayer/ARM64

在这里插入图片描述

2.2 新建文件

2.2.1 HL2RmStreamUnityPlugin.dll

将前面生成解决方案编译好的文件HL2RmStreamUnityPlugin.dll拷贝到新建的ARM64文件夹下
在这里插入图片描述

2.2.2 StreamerHL2.cs

Scripts文件夹中新建C#脚本文件,命名为:StreamerHL2.cs

StreamerHL2.cs内容如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Runtime.InteropServices;

public class StreamerHL2 : MonoBehaviour
{
    [DllImport("HL2RmStreamUnityPlugin", EntryPoint = "Initialize", CallingConvention = CallingConvention.StdCall)]
	public static extern void InitializeDll(); 

    void Start(){InitializeDll();}
    void Update(){}

}


2.2.3 挂载StreamerHL2.cs脚本

将StreamerHL2.cs文件拖到Main camera上实现挂载
在这里插入图片描述

3. 配置Unity的package文件中的兼容性

3.1 添加组件

点击工具栏FileBuild SettingsPlayer SettingsPlayerCapabilities

勾选InternetClient, InternetClientServer, PrivateNetworkClientServer, WebCam, SpatialPerception
在这里插入图片描述在这里插入图片描述

3.2 编译unity工程

注意架构选择的是ARM64. 然后build到一个新文件夹
新文件夹最好是在此项目工程文件夹里
在这里插入图片描述

3.3 配置Unity的package文件中的兼容性

打开编译好的Unity工程文件夹中的“Package.appxmanifest”文件,按照官方文档修改3处内容(注意空格)
在这里插入图片描述

1)将如下字段添加到 Package

xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"

在这里插入图片描述

2)添加“rescap”字段
还是刚才的Package这一行中,找到"IgnorableNamespaces"的属性,添加“rescap”字段
在这里插入图片描述

3)在 Capabilityes添加如下字段

<rescap:Capability Name="perceptionSensorsExperimental" />

在这里插入图片描述

修改后的Package.appxmanifest文件如下,RMSTest可更换为自己的文件名
Camera_Stream:unity编译后的程序名(默认项目名)
DefaultCompany:unity项目默认公司名

<?xml version="1.0" encoding="utf-8"?>
<Package xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:iot="http://schemas.microsoft.com/appx/manifest/iot/windows10" xmlns:mobile="http://schemas.microsoft.com/appx/manifest/mobile/windows10" IgnorableNamespaces="uap uap2 uap3 uap4 mp mobile iot rescap" xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10">
  <Identity Name="Template3D" Publisher="CN=DefaultCompany" Version="1.0.0.0" />
  <mp:PhoneIdentity PhoneProductId="edd907fa-2c4f-49b2-89a0-9fa371043c33" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
  <Properties>
    <DisplayName>Camera_Stream</DisplayName>
    <PublisherDisplayName>DefaultCompany</PublisherDisplayName>
    <Logo>Assets\StoreLogo.png</Logo>
  </Properties>
  <Dependencies>
    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.10240.0" MaxVersionTested="10.0.19041.0" />
  </Dependencies>
  <Resources>
    <Resource Language="x-generate" />
  </Resources>
  <Applications>
    <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="Template3D.App">
      <uap:VisualElements DisplayName="Camera_Stream" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="Template_3D" BackgroundColor="transparent">
        <uap:DefaultTile ShortName="Camera_Stream" Wide310x150Logo="Assets\Wide310x150Logo.png" />
        <uap:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="#FFFFFF" />
        <uap:InitialRotationPreference>
          <uap:Rotation Preference="landscape" />
          <uap:Rotation Preference="landscapeFlipped" />
          <uap:Rotation Preference="portrait" />
          <uap:Rotation Preference="portraitFlipped" />
        </uap:InitialRotationPreference>
      </uap:VisualElements>
    </Application>
  </Applications>
  <Capabilities>
	<rescap:Capability Name="perceptionSensorsExperimental" />
    <Capability Name="internetClient" />
    <Capability Name="internetClientServer" />
    <Capability Name="privateNetworkClientServer" />
    <uap2:Capability Name="spatialPerception" />
    <DeviceCapability Name="webcam" />
  </Capabilities>
</Package>

4. 项目部署

可以参考我之前的博文:Holoens2开发环境配置及项目程序部署里的 3.程序部署到hololens2设备(VS部署)

5. python获取数据流

5.1 hololens2_simpleclient.py

打开下载好的github工程中的hololens2_simpleclient.py文件

Your Location\HoloLens2-Unity-ResearchModeStreamer\py\hololens2_simpleclient.py
在这里插入图片描述

5.2 修改局域网

修改hololens2_simpleclient.py文件里的ip地址:HOST
这个是hololens2的IPv4 地址
在这里插入图片描述

5.3 查看结果

1)部署到hololens2里的程序已打开
2)hololens2与PC端处在同一个局域网下(hololens2可以连接PC端热点)
3)PC端的防火墙已关
4)运行修改完后的hololens2_simpleclient.py文件
在这里插入图片描述

运行时若出现** WinError 10060错误提示,解决方法可参考文章后面的常见错误**

5.3 将图像信息保存为图片

以下为修改后的hololens2_simpleclient.py文件,运行前同样要修改局域网

import os
import socket
import struct
import abc
import threading
import time
from datetime import datetime, timedelta
from collections import namedtuple, deque
from enum import Enum
import numpy as np
import cv2

# np.warnings.filterwarnings('ignore')
# 上行代码更改如下
import warnings
warnings.filterwarnings('ignore', category=np.VisibleDeprecationWarning)

# Definitions
# Protocol Header Format
# see https://docs.python.org/2/library/struct.html#format-characters
VIDEO_STREAM_HEADER_FORMAT = "@qIIII18f"

VIDEO_FRAME_STREAM_HEADER = namedtuple(
    'SensorFrameStreamHeader',
    'Timestamp ImageWidth ImageHeight PixelStride RowStride fx fy '
    'PVtoWorldtransformM11 PVtoWorldtransformM12 PVtoWorldtransformM13 PVtoWorldtransformM14 '
    'PVtoWorldtransformM21 PVtoWorldtransformM22 PVtoWorldtransformM23 PVtoWorldtransformM24 '
    'PVtoWorldtransformM31 PVtoWorldtransformM32 PVtoWorldtransformM33 PVtoWorldtransformM34 '
    'PVtoWorldtransformM41 PVtoWorldtransformM42 PVtoWorldtransformM43 PVtoWorldtransformM44 '
)

RM_STREAM_HEADER_FORMAT = "@qIIII16f"

RM_FRAME_STREAM_HEADER = namedtuple(
    'SensorFrameStreamHeader',
    'Timestamp ImageWidth ImageHeight PixelStride RowStride '
    'rig2worldTransformM11 rig2worldTransformM12 rig2worldTransformM13 rig2worldTransformM14 '
    'rig2worldTransformM21 rig2worldTransformM22 rig2worldTransformM23 rig2worldTransformM24 '
    'rig2worldTransformM31 rig2worldTransformM32 rig2worldTransformM33 rig2worldTransformM34 '
    'rig2worldTransformM41 rig2worldTransformM42 rig2worldTransformM43 rig2worldTransformM44 '
)

# Each port corresponds to a single stream type
VIDEO_STREAM_PORT = 23940
AHAT_STREAM_PORT = 23941

HOST = '192.168.137.34'

HundredsOfNsToMilliseconds = 1e-4
MillisecondsToSeconds = 1e-3


class SensorType(Enum):
    VIDEO = 1
    AHAT = 2
    LONG_THROW_DEPTH = 3
    LF_VLC = 4
    RF_VLC = 5


class FrameReceiverThread(threading.Thread):
    def __init__(self, host, port, header_format, header_data):
        super(FrameReceiverThread, self).__init__()
        self.header_size = struct.calcsize(header_format)
        self.header_format = header_format
        self.header_data = header_data
        self.host = host
        self.port = port
        self.latest_frame = None
        self.latest_header = None
        self.socket = None

        # ----------------------------------------添加锁对象---------------------------------------
        self.lock = threading.Lock()  # 新增这一行

    def get_data_from_socket(self):
        # read the header in chunks
        reply = self.recvall(self.header_size)

        if not reply:
            print('ERROR: Failed to receive data from stream.')
            return

        data = struct.unpack(self.header_format, reply)
        header = self.header_data(*data)

        # read the image in chunks
        image_size_bytes = header.ImageHeight * header.RowStride
        image_data = self.recvall(image_size_bytes)

        return header, image_data

    def recvall(self, size):
        msg = bytes()
        while len(msg) < size:
            part = self.socket.recv(size - len(msg))
            if part == '':
                break  # the connection is closed
            msg += part
        return msg

    def start_socket(self):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect((self.host, self.port))
        # send_message(self.socket, b'socket connected at ')
        print('INFO: Socket connected to ' + self.host + ' on port ' + str(self.port))

    def start_listen(self):
        t = threading.Thread(target=self.listen)
        t.start()

    @abc.abstractmethod
    def listen(self):
        return

    @abc.abstractmethod
    def get_mat_from_header(self, header):
        return


class VideoReceiverThread(FrameReceiverThread):
    def __init__(self, host):
        super().__init__(host, VIDEO_STREAM_PORT, VIDEO_STREAM_HEADER_FORMAT,
                         VIDEO_FRAME_STREAM_HEADER)

    def listen(self):
        while True:
            # self.latest_header, image_data = self.get_data_from_socket()
            # self.latest_frame = np.frombuffer(image_data, dtype=np.uint8).reshape((self.latest_header.ImageHeight,
            #                                                                        self.latest_header.ImageWidth,
            #                                                                        self.latest_header.PixelStride))
            header, image_data = self.get_data_from_socket()
            with self.lock:  # 现在可以使用锁了
                self.latest_header = header
                self.latest_frame = np.frombuffer(image_data, dtype=np.uint8).reshape(
                    (header.ImageHeight, header.ImageWidth, header.PixelStride))


    # def get_mat_from_header(self, header):
    #     pv_to_world_transform = np.array(header[7:24]).reshape((4, 4)).T
    #     return pv_to_world_transform


class AhatReceiverThread(FrameReceiverThread):
    def __init__(self, host):
        super().__init__(host,
                         AHAT_STREAM_PORT, RM_STREAM_HEADER_FORMAT, RM_FRAME_STREAM_HEADER)

    def listen(self):
        while True:
            # self.latest_header, image_data = self.get_data_from_socket()
            # self.latest_frame = np.frombuffer(image_data, dtype=np.uint16).reshape((self.latest_header.ImageHeight,
            #                                                                         self.latest_header.ImageWidth))
            header, image_data = self.get_data_from_socket()
            with self.lock:  # 现在可以使用锁了
                self.latest_header = header
                self.latest_frame = np.frombuffer(image_data, dtype=np.uint16).reshape(
                    (header.ImageHeight, header.ImageWidth))

    # def get_mat_from_header(self, header):
    #     rig_to_world_transform = np.array(header[5:22]).reshape((4, 4)).T
    #     return rig_to_world_transform


if __name__ == '__main__':

    # 创建保存目录
    os.makedirs("rgb", exist_ok=True)
    os.makedirs("depth", exist_ok=True)

    video_receiver = VideoReceiverThread(HOST)
    video_receiver.start_socket()

    ahat_receiver = AhatReceiverThread(HOST)
    ahat_receiver.start_socket()

    video_receiver.start_listen()
    ahat_receiver.start_listen()

    last_save_time = time.time()

    # while True:
        # if np.any(video_receiver.latest_frame) and np.any(ahat_receiver.latest_frame):
        #
        #     cv2.imshow('Photo Video Camera Stream', video_receiver.latest_frame)
        #     if cv2.waitKey(1) & 0xFF == ord('q'):
        #         break
        #
        #     cv2.imshow('Depth Camera Stream', ahat_receiver.latest_frame)
        #     if cv2.waitKey(1) & 0xFF == ord('q'):
        #         break

    try:
        while True:
            # 使用线程锁确保获取完整的帧
            with video_receiver.lock, ahat_receiver.lock:
                if video_receiver.latest_frame is not None and ahat_receiver.latest_frame is not None:
                    # 显示图像
                    cv2.imshow('RGB Stream', video_receiver.latest_frame)
                    depth_colormap = cv2.applyColorMap(
                        cv2.convertScaleAbs(ahat_receiver.latest_frame, alpha=0.03),
                        cv2.COLORMAP_JET
                    )
                    cv2.imshow('Depth Stream', depth_colormap)

                    # 保存逻辑(每秒10次)
                    current_time = time.time()
                    if current_time - last_save_time >= 0.1:
                        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]

                        # 保存RGB(BGR格式直接保存)
                        rgb_path = f"rgb/{timestamp}_rgb.jpg"
                        cv2.imwrite(rgb_path, video_receiver.latest_frame)

                        # 保存深度原始数据(16位PNG)
                        depth_path = f"depth/{timestamp}_depth.png"
                        cv2.imwrite(depth_path, ahat_receiver.latest_frame)

                        print(f"Saved: {rgb_path}, {depth_path}")
                        last_save_time = current_time

            # 退出控制
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
    finally:
        cv2.destroyAllWindows()
        print("程序正常退出")




6. 解决实时数据流传输

本节参考文章Hololens2初入——解决HL真机到PC图像传输的实时性问题

6.1 思路一:降低hololens2往PC端发送数据的频率

HL2RmStreamUnityPlugin.cpp文件中:
1)在第60行左右找到以下代码,并按图示更改,将第一个传输参数改为2000000。这边更改的是彩色图像的传输频率
(这里的 2000000是200ms的意思,程序里面采用CPU时间单位,即百纳秒,1ms = 10^4百纳秒即5Hz)
在这里插入图片描述
若没有这三个参数
找到

co_await m_pVideoFrameProcessor->InitializeAsync(m_pVideoFrameStreamer)

改为

co_await m_pVideoFrameProcessor->InitializeAsync(m_pVideoFrameStreamer, 2000000);

2)找到134行按照下面图示更改,这边更改的是深度图像的传输频率,同样改为5Hz
在这里插入图片描述

6.2 思路二:增大wifi的通讯带宽,提升接收频率(换个好的网卡)

普通无线网卡 1-2Hz
5G频段,1200MB 的wifi6模块 USB接口 5Hz-10Hz
1200MB路由 +高速网线 5Hz左右
PCI-E 5G频段 3200MB 10-15Hz左右

6.3 思路三:压缩数据,减少传输字节

常见错误

更多常见错误地址

E1696

E1696 无法打开源文件 “stdio.h”

解决方法:
更新一下SDK
1)打开Visual Studio Installer,点击修改
在这里插入图片描述
2)安装详细信息中自己系统对应的SDK,点击修改即可
在这里插入图片描述

WinError 10060

方法来源
解决方法:
1.先看研究模式有没有打开。
2.Unity buildsetting中的几个InternetClient, InternetClientServer, PrivateNetworkClientServer, WebCam, SpatialPerception确认勾选上

DllNotFoundException: HL2RmStreamUnityPlugin

DllNotFoundException: HL2RmStreamUnityPlugin StreamerHL2.Start () (at Assets/Scripts/StreamerHL2.cs:12)Failed to pause IContinuousRecognitionSession (hr = 0x80131509)

不需要在unity中运行工程,因为我们添加的是ARM64的动态链接库,在PC端运行的话会报加载不了的错误,此错误可以忽略,以下方法只是确保文件存在且导入进项目里
解决方法:
1)确保HL2RmStreamUnityPlugin.dll文件存在于项目的Assets/Plugins/WSAPlayer/ARM64文件夹中,HL2RmStreamUnityPlugin.dll文件没有问题
2)在StreamerHL2.cs中,检查调用DLL的代码,确保名称正确
确定脚本正确挂载

 [DllImport("HL2RmStreamUnityPlugin", EntryPoint = "Initialize", CallingConvention = CallingConvention.StdCall)]

3)清理并重新导入项目
删除Library文件夹(位于项目根目录),然后重新打开Unity。Unity会重新生成所有缓存文件。
在Unity编辑器中,右键点击Assets文件夹并选择Reimport All,确保所有资源重新导入。
4)加载原场景
在原场景运行前,可先取消StreamerHL2脚本复选框,运行没问题后再勾选运行
原场景(.unity文件)路径:

Assets文件夹
或者Assets/Scenes文件夹

在这里插入图片描述

参考文章:
Hololens2 初入——获取彩色和深度图像数据流,并传递到程序中(不是网页浏览)
HoloLens2的彩色和深度数据流通过主机获取
主机端实时获取Hololens2的RGBD数据流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值