HUSKYLENS 2 实时图传教程

1. 什么是实时图传

实时图传可以将 HUSKYLENS 2 摄像头的画面传输到电脑,方便在更大的屏幕上查看,也可以在 Python 或 Mind+ 中使用图像。

HUSKYLENS 2 提供 WebRTC 和 RTSP 两种图传协议。首次使用,建议从 WebRTC + 浏览器开始:完成设备联网后,在浏览器中输入设备地址即可查看画面,无需编写程序或安装专门的播放器。

本教程先介绍联网与 IP 地址的准备,再介绍浏览器图传,最后介绍 Python、Mind+ 和 VLC 等其他实现方式。

在设备屏幕上查找“实时图传”功能。如果没有找到,请先按照HUSKYLENS 2 固件更新教程更新固件。

2. 实时图传准备

电脑通过 HUSKYLENS 2 的 IP 地址访问图传服务。你可以选择 USB 有线连接或 WiFi 无线连接,完成其中一种即可继续。

连接方式 需要准备 后续填写的 IP
USB 有线连接 支持数据传输的 Type-C 数据线;Windows 电脑需要识别 RNDIS 网络接口 设备屏幕显示的有线 IP,本文示例为 192.168.88.1
WiFi 无线连接 HUSKYLENS 2 WiFi 模块;设备和电脑连接到同一个可互通的 WiFi 网络 设备屏幕显示的无线 IP,本文示例为 192.168.9.181

2.1 有线图传准备

连接设备

使用 Type-C 数据线,将 HUSKYLENS 2 连接到电脑。

USB 有线图传通过网络连接传输画面。Windows 电脑需要正确识别设备的 RNDIS 网络接口,需要安装对应的RNDIS驱动。

安装 RNDIS 驱动

注:如果电脑已经能够识别设备的 RNDIS 网络接口,可以跳过驱动安装。

以下以 Windows 11 为例,结合图示完成安装:

  1. 右键点击 Windows 开始按钮,打开“设备管理器”。

RNDIS 驱动安装步骤 1

  1. 找到连接 HUSKYLENS 2 后出现的未识别设备或 RNDIS 设备,右键选择“更新驱动程序”。

RNDIS 驱动安装步骤 2

选择“浏览我的电脑以查找驱动程序”,再选择“让我从计算机上的可用驱动程序列表中选取”。

RNDIS 驱动安装步骤 3

RNDIS 驱动安装步骤 4

  1. 按照下方图示,在网络适配器相关选项中选择 RNDIS 兼容驱动,完成安装。不同系统显示名称可能有所不同。

RNDIS 驱动安装步骤 5

RNDIS 驱动安装步骤 6

  1. 安装完成后,确认设备出现在“网络适配器”列表中,且没有黄色警告标记。

RNDIS 驱动安装步骤 7

如果按照图示仍无法安装,请根据系统版本联系技术支持。

驱动安装完成后,保持 USB 连接。后续开启图传并进入模型界面时,查看设备显示的有线 IP 地址。

如下,通过Type-C线连接电脑与Huskylens 2后有线IP地址固定为192.168.88.1。

开启图传后进入人脸识别模型

2.2 无线图传准备

  1. 准备HUSKYLENS 2 WiFi 模块,在设备断电时按照模块说明安装,然后为设备供电。安装教程:点击查看。
  2. 在 HUSKYLENS 2 的设置中进入 WiFi 设置,选择电脑所连接的同一WiFi 名称,手动输入密码并连接。
  3. 确认设备连接成功,并确认电脑也连接到同一个 WiFi。该网络需要允许电脑与设备相互访问。
  4. 后续开启图传并进入模型界面时,查看设备显示的无线 IP 地址,记录下来。

重新连接 WiFi 后,设备 IP 可能变化,请以设备当前显示的地址为准。如果屏幕同时显示有线和无线 IP,请选择与你当前连接方式对应的地址。

3. 使用 WebRTC 在浏览器中查看实时画面

完成第 2 节中的任意一种连接方式后,按下面的步骤操作。有线和无线的浏览器操作相同,只需填写对应的设备 IP。

3.1 开启 WebRTC 图传

  1. 在 HUSKYLENS 2 中进入“实时图传”。

进入实时图传功能

  1. 打开 WebRTC 图传开关,点击确认。

开启 WebRTC 图传

  1. 设备返回主界面,右上角显示实时图传图标。

主界面实时图传图标

  1. 打开一个模型,例如“人脸识别”,查看屏幕显示的 IP 地址。第一行为有线IP地址。第2行为无线IP地址。

开启图传后进入人脸识别模型

3.2 输入地址并播放画面

在电脑浏览器的地址栏输入对应地址,按回车打开页面:

当前连接方式 浏览器地址示例 如何填写
USB 有线 http://192.168.88.1 使用设备显示的有线 IP
WiFi 无线 http://192.168.9.231 将示例 IP 替换为设备显示的无线 IP

地址格式为 http://设备IP。进入页面后,点击 play stream,即可查看实时画面。

浏览器图传页面,点击 play stream 播放

如果页面无法打开,先检查 IP 是否填写正确:有线连接检查数据线和 RNDIS 网络接口;无线连接检查设备与电脑是否已连接同一个可互通的 WiFi。页面能打开但没有画面时,检查设备是否已开启 WebRTC,并进入模型界面。

3.3 结束图传

关闭浏览器页面即可结束电脑端观看。如需关闭设备图传,在 HUSKYLENS 2 上点击实时图传图标,关闭 WebRTC 开关并确认。

4. 其他实现

以下方法都使用第 2 节中准备好的设备 IP,无需重新按有线、无线分别操作。

实现方式 设备需开启的协议 用途
Python 接收 WebRTC WebRTC 在 Python 中获取和处理图像
Mind+ WebRTC 按照 Mind+ 教程使用实时画面
VLC 播放 RTSP RTSP 使用播放器查看画面
Python 接收 RTSP RTSP 使用 OpenCV 读取和处理图像

4.1 WebRTC 实时图传

4.1.1 Mind+ 实时图传

  1. 从Mind+ 下载页面下载并安装 Mind+ V2.x 系列。
  2. 完成第 2 节中的设备连接,按照 3.1 开启 WebRTC 并进入模型界面。
  3. 按照Mind+ 数据采集与采集设备指南中的 HUSKYLENS 2 说明进行操作。需要填写设备 IP 时,有线连接使用有线 IP,无线连接使用无线 IP。

结束使用后,如需关闭设备图传,请关闭 HUSKYLENS 2 的 WebRTC 开关。

4.1.2 Python实时图传

先按照 3.1 开启 WebRTC 并进入模型界面,再运行以下示例。

本教程使用 HUSKYLENS_WebRTC.py,通过设备上的 Janus 服务建立 WebRTC 连接,并使用 OpenCV 显示收到的图像。

第一步:准备 Python 环境并安装依赖

在电脑上下载并安装 Python。

Python 官方下载地址:点击查看

安装时,建议勾选 Add Python to PATH(将 Python 添加到系统环境变量),然后按照安装向导完成安装。

安装完成后,按下键盘上的 Win + R,输入 cmd,按下回车键打开命令提示符(CMD)。

在终端中输入以下命令:

python --version

如果终端显示 Python 版本号,例如 Python 3.x.x,则表示 Python 已安装成功。如果提示找不到 Python,请检查是否已正确安装并添加到系统环境变量。

在终端输入以下指令按下回车键执行:

python -m pip install aiohttp aiortc av opencv-python numpy

安装成功显示如下。

第二步:保存示例代码并修改 IP

复制以下代码,保存为 HUSKYLENS_WebRTC.py。将 JANUS_URL 中的 IP 改为第 2 节所选连接方式对应的设备 IP,保留端口 8088、路径 /janus 和流编号 99。

有线示例:http://192.168.88.1:8088/janus;无线示例:http://192.168.9.181:8088/janus(请替换为实际无线 IP)。


# aio.py — HUSKYLENS 2 Janus 实时视频流接收程序
# 功能:通过 Janus Streaming + WebRTC 接收视频流,
#       并使用 OpenCV 在电脑上实时显示画面。

import asyncio
import aiohttp
import json
import re
import cv2
import av
import traceback
import os
import sys
from aiortc import RTCPeerConnection, RTCSessionDescription, RTCRtpSender, MediaStreamTrack

# ==================== 1. 基本配置 ====================

# Janus 服务器地址,请根据 HUSKYLENS 2 的实际 IP 地址修改
JANUS_URL = "http://192.168.88.1:8088/janus"

# Janus Streaming 插件的视频流挂载点 ID
MOUNTPOINT_ID = 99


# ==================== 2. 工具函数 ====================

def txn():
    """生成随机事务 ID,用于区分不同的 Janus API 请求"""
    import random
    return str(random.randint(1, 999999999))


def sanitize_remove_rtx(sdp: str) -> str:
    """
    处理 SDP 协商信息。

    移除负载类型 97 对应的 RTX 信息,
    并从指定的媒体格式列表中移除 97,
    用于兼容当前视频流的协商配置。
    """
    s = re.sub(r"\r?\na=rtpmap:97.*", "", sdp)
    s = re.sub(r"\r?\na=fmtp:97.*", "", s)
    s = s.replace("96 97", "96")
    return s


def print_codecs():
    """打印本地 aiortc 支持的视频编解码器信息"""
    try:
        caps = RTCRtpSender.getCapabilities("video")

        print("\n========== 本地视频编解码器 ==========")

        for c in caps.codecs:
            print(
                "编码类型:", getattr(c, "mimeType", None),
                "时钟频率:", getattr(c, "clockRate", None),
                "SDP 参数:", getattr(c, "sdpFmtpLine", None),
                "其他参数:", getattr(c, "parameters", None)
            )

    except Exception as e:
        print("读取本地编解码器信息失败:", e)


# ==================== 3. 视频轨道 ====================

class BlackFrameTrack(MediaStreamTrack):
    """
    创建一个黑色视频轨道。

    默认分辨率:320 × 240
    默认帧率:5 FPS

    该类作为备用视频轨道保留,
    当前主程序没有主动使用它。
    """

    kind = "video"

    def __init__(self, width=320, height=240, fps=5):
        super().__init__()

        import numpy as np

        # 创建全黑图像
        self._frame = np.zeros(
            (height, width, 3),
            dtype="uint8"
        )

        # 计算每帧的时间间隔
        self._period = 1.0 / fps

    async def recv(self):
        """生成并返回一帧黑色视频画面"""

        frame = av.VideoFrame.from_ndarray(
            self._frame,
            format="bgr24"
        )

        await asyncio.sleep(self._period)

        return frame


# ==================== 4. 连接保活 ====================

async def rtcp_keepalive(pc):
    """
    尝试定时发送 RTCP 接收报告(RR)。

    每隔 2 秒检查一次视频接收器。
    注意:send_rtcp_rr() 并非所有 aiortc 版本
    都支持的公开接口,实际是否发送取决于版本。
    """

    while True:
        await asyncio.sleep(2)

        try:
            for t in pc.getTransceivers():

                if t.receiver:
                    try:
                        await t.receiver.send_rtcp_rr()
                    except Exception:
                        pass

        except Exception:
            pass


async def janus_keepalive(http, sid):
    """
    定时向 Janus 服务器发送保活请求。

    每隔 5 秒发送一次 keepalive,
    避免 Janus 会话因长时间无请求而超时。
    """

    while True:
        await asyncio.sleep(5)

        try:
            await janus_post(
                http,
                f"{JANUS_URL}/{sid}",
                {
                    "janus": "keepalive",
                    "transaction": txn()
                }
            )

            print("已发送 Janus 保活请求")

        except Exception as e:
            print("Janus 保活请求失败:", e)


# ==================== 5. 视频接收与显示 ====================

async def video_consumer(track):
    """
    接收 WebRTC 视频轨道中的画面。

    将接收到的视频帧转换为 OpenCV 图像,
    并在电脑上实时显示。

    在视频窗口中按 Q 键退出视频接收循环。
    """

    print("视频接收任务已启动")

    try:
        while True:

            # 接收一帧视频
            f = await track.recv()

            # 转换为 OpenCV 使用的 BGR 图像格式
            img = f.to_ndarray(format="bgr24")

            # 显示视频画面
            cv2.imshow("Janus Stream", img)

            # 检测键盘输入,按 Q 退出当前视频循环
            if cv2.waitKey(1) & 0xFF == ord("q"):
                break

    except Exception as e:
        print("视频接收结束:", e)


# ==================== 6. Janus HTTP 通信 ====================

async def janus_post(session, url, data):
    """
    向 Janus 服务器发送 HTTP POST 请求。

    参数:
        session:aiohttp 会话
        url:请求地址
        data:请求数据

    返回:
        Janus 服务器返回的 JSON 数据
    """

    async with session.post(
        url,
        data=json.dumps(data)
    ) as resp:

        return await resp.json()


async def janus_get(session, url):
    """
    向 Janus 服务器发送 HTTP GET 请求。

    主要用于获取 Janus 返回的异步事件,
    包括 WebRTC SDP Offer 等信息。
    """

    async with session.get(url) as resp:
        return await resp.json()


# ==================== 7. 程序自动重启 ====================

async def restart_program():
    """
    检测到 WebRTC 连接断开时,尝试重新启动程序。

    注意:该方式适用于从 .py 文件启动的程序。
    如果通过 python - 或终端内联代码执行,
    自动重启可能无法恢复原程序。
    """

    print("检测到连接断开,准备重新启动程序...")

    await asyncio.sleep(1)

    os.execv(
        sys.executable,
        ['python'] + sys.argv
    )


# ==================== 8. 主程序 ====================

async def main():

    # 创建异步 HTTP 客户端会话
    async with aiohttp.ClientSession() as http:

        # ---------- 步骤 1:创建 Janus 会话 ----------

        print("正在连接 Janus 服务器...")

        r = await janus_post(
            http,
            JANUS_URL,
            {
                "janus": "create",
                "transaction": txn()
            }
        )

        # 获取 Janus Session ID
        sid = r["data"]["id"]

        print("Janus 会话创建成功,Session ID:", sid)

        # ---------- 步骤 2:加载 Streaming 插件 ----------

        r = await janus_post(
            http,
            f"{JANUS_URL}/{sid}",
            {
                "janus": "attach",
                "plugin": "janus.plugin.streaming",
                "transaction": txn()
            }
        )

        # 获取插件 Handle ID
        handle = r["data"]["id"]

        print("Streaming 插件连接成功")
        print("Session:", sid, "Handle:", handle)

        # ---------- 步骤 3:启动 Janus 会话保活 ----------

        asyncio.create_task(
            janus_keepalive(http, sid)
        )

        # ---------- 步骤 4:订阅指定视频流 ----------

        await janus_post(
            http,
            f"{JANUS_URL}/{sid}/{handle}",
            {
                "janus": "message",
                "body": {
                    "request": "watch",
                    "id": MOUNTPOINT_ID
                },
                "transaction": txn()
            }
        )

        print("已发送视频流订阅请求")
        print("视频流 ID:", MOUNTPOINT_ID)

        # ---------- 步骤 5:等待 SDP Offer ----------

        offer = None

        print("正在等待服务器返回 SDP Offer...")

        while offer is None:

            # 获取 Janus 异步事件
            evs = await janus_get(
                http,
                f"{JANUS_URL}/{sid}?rid=1&maxev=10"
            )

            # 兼容列表和事件对象两种返回结构
            evlist = (
                evs
                if isinstance(evs, list)
                else evs.get("events", [evs])
            )

            # 查找包含 SDP 信息的事件
            for ev in evlist:

                if "jsep" in ev:
                    offer = ev["jsep"]
                    break

            await asyncio.sleep(0.05)

        print("已收到 SDP Offer")

        # ---------- 步骤 6:处理 SDP 信息 ----------

        raw_sdp = offer["sdp"]

        # 移除指定 RTX 信息
        sdp1 = sanitize_remove_rtx(raw_sdp)

        print("\n========== SDP Offer(处理后)==========")
        print(sdp1[:800])
        print("========================================\n")

        # ---------- 步骤 7:创建 WebRTC 连接 ----------

        pc = RTCPeerConnection()

        print("WebRTC PeerConnection 已创建")

        # 启动 RTCP 保活任务
        asyncio.create_task(
            rtcp_keepalive(pc)
        )

        # ---------- 步骤 8:监听视频轨道 ----------

        @pc.on("track")
        def on_track(track):
            """
            当 WebRTC 收到新的媒体轨道时触发。
            如果是视频轨道,则启动视频接收任务。
            """

            print("收到媒体轨道:", track.kind)

            if track.kind == "video":

                asyncio.create_task(
                    video_consumer(track)
                )

        # ---------- 步骤 9:监听连接状态 ----------

        @pc.on("iceconnectionstatechange")
        def on_ice():
            """
            监听 ICE 连接状态。

            当连接关闭、失败或断开时,
            尝试重新启动程序。
            """

            print("ICE 连接状态:", pc.iceConnectionState)

            if pc.iceConnectionState in (
                "closed",
                "failed",
                "disconnected"
            ):

                asyncio.create_task(
                    restart_program()
                )

        # ---------- 步骤 10:设置远程 SDP ----------

        try:

            print("正在设置远程 SDP...")

            # 打印本地支持的视频编码格式
            print_codecs()

            # 设置 Janus 返回的远程 SDP Offer
            await pc.setRemoteDescription(
                RTCSessionDescription(
                    sdp1,
                    "offer"
                )
            )

            print("远程 SDP 设置成功")

        except Exception as e:

            print("远程 SDP 设置失败:", e)

            traceback.print_exc()

            return

        # ---------- 步骤 11:创建 SDP Answer ----------

        print("正在生成 SDP Answer...")

        answer = await pc.createAnswer()

        await pc.setLocalDescription(answer)

        print("本地 SDP Answer 生成成功")

        # ---------- 步骤 12:发送 SDP Answer ----------

        await janus_post(
            http,
            f"{JANUS_URL}/{sid}/{handle}",
            {
                "janus": "message",

                "body": {
                    "request": "start"
                },

                "jsep": {
                    "type": "answer",
                    "sdp": pc.localDescription.sdp
                },

                "transaction": txn()
            }
        )

        print("SDP Answer 已发送")
        print("正在等待视频流...")
        print("视频窗口打开后,按 Q 键退出视频显示")
        print("在终端按 Ctrl+C 可停止程序")

        # ---------- 步骤 13:保持程序运行 ----------

        while True:
            await asyncio.sleep(1)


# ==================== 9. 程序入口 ====================

if __name__ == "__main__":

    # 启动 asyncio 事件循环并执行主程序
    asyncio.run(main())

第三步:运行代码

在保存 HUSKYLENS_WebRTC.py 的文件夹中,点击资源管理器地址栏,输入 cmd 并按回车,打开终端。

在终端输入以下命令,按下回车执行:

python HUSKYLENS_WebRTC.py

程序会连接设备并进行 WebRTC 协商。成功收到视频后,会显示名为 Janus Stream 的画面窗口。

当前示例中,在视频窗口按 q 只会结束视频接收循环,主程序仍会继续运行。结束整个程序时,请回到运行程序的终端,按 Ctrl+C。

如需关闭设备图传,请同时关闭 HUSKYLENS 2 的 WebRTC 图传开关。

4.2 RTSP实时图传

4.2.1 VLC 应用实时图传

准备:开启 RTSP 图传

在 HUSKYLENS 2 中进入“实时图传”,打开 RTSP 开关并确认。返回主界面后打开一个模型,记录对应连接方式的设备 IP。

开启 RTSP 图传

RTSP 地址格式为 rtsp://设备IP:8554/live:

当前连接方式 RTSP 地址示例
USB 有线 rtsp://192.168.88.1:8554/live
WiFi 无线 rtsp://192.168.9.181:8554/live(替换为实际无线 IP)

第一步:安装 VLC

通过 VLC 官方下载页面下载并安装适用于电脑系统的版本。

第二步:打开网络串流

启动 VLC,在菜单中选择“媒体 → 打开网络串流”。

VLC 打开网络串流入口

填写设备的 RTSP 地址。下面是有线示例;使用 WiFi 时,将 IP 替换为设备屏幕显示的无线 IP:

rtsp://192.168.88.1:8554/live

VLC 输入 RTSP 地址

第三步:查看画面

点击播放,等待 VLC 显示实时画面。

VLC 显示实时画面

第四步:结束图传

停止播放或关闭 VLC。如需关闭设备图传,请在 HUSKYLENS 2 上关闭 RTSP 图传开关。

4.2.2 Python实时图传

先按照 4.2.1 的准备步骤开启 RTSP 并进入模型界面。

第一步:安装依赖

如果尚未安装 Python,请参考 4.1 的 Python 环境准备步骤。打开终端,执行:

python -m pip install opencv-python

第二步:保存代码并修改 IP

复制以下代码,保存为 HUSKYLENS_RTSP.py。将 DEVICE_IP 改为设备当前的 IP:有线填写有线 IP,无线填写无线 IP。

import cv2

DEVICE_IP = "192.168.88.1"  # 无线连接时改为设备实际的无线 IP
rtsp_url = f"rtsp://{DEVICE_IP}:8554/live"
cap = cv2.VideoCapture(rtsp_url)

try:
    if not cap.isOpened():
        raise RuntimeError("无法打开视频流,请检查设备 IP、网络连接和 RTSP 开关。")

    while True:
        ret, frame = cap.read()
        if not ret:
            print("未能读取画面,请检查设备及网络连接。")
            break
        # 可在这里添加自己的图像处理代码
        cv2.imshow("RTSP Live", frame)
        if cv2.waitKey(1) & 0xFF == ord("q"):
            break
finally:
    cap.release()
    cv2.destroyAllWindows()

第三步:运行程序

在代码所在文件夹打开终端,执行:

python HUSKYLENS_RTSP.py

成功读取视频后,会显示 RTSP Live 画面窗口。代码中的 frame 是本次读取到的图像,可以用于后续处理。

选中视频窗口后按 q 退出,或在终端按 Ctrl+C 停止程序。如需关闭设备图传,请同时关闭 HUSKYLENS 2 的 RTSP 开关。