diff --git a/Cpp_example/.gitignore b/Cpp_example/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b5ccccc4b4e16e122a303e88d6dd3f6259ab7ed8 --- /dev/null +++ b/Cpp_example/.gitignore @@ -0,0 +1,11 @@ +TestFacePlate_lcd/ +T-Helmetdetect/ +T-electronic_fence/ +T-smart_inspection/ +RGA/ +T/ +D17_handmark/ +D18_facemesh/ +D18_shufflenet-backbone/ +D19-tinpypose/ +Vision/ diff --git a/Cpp_example/D16_LPRNet/README.md b/Cpp_example/D16_LPRNet/README.md index aeeac4de2c409a309938ff6f5ee32286807b0537..63507f16da12a399420386989eb54a6a104d13b6 100644 --- a/Cpp_example/D16_LPRNet/README.md +++ b/Cpp_example/D16_LPRNet/README.md @@ -191,7 +191,7 @@ chmod 777 Test-lprnet 程序运行后,控制台会输出识别到的车牌号码: ``` -The car plate is 京A12345 +The car plate is 湘F 6CL03 ``` ## 5. 总结 diff --git a/F&Q/faq.md b/F&Q/faq.md index 8d2e919818711df2a43e1ad3409a4ffa147685b7..124dd0232e817756464940d45016d7babd13a140 100644 --- a/F&Q/faq.md +++ b/F&Q/faq.md @@ -87,4 +87,15 @@ git clone https://gitee.com/LockzhinerAI/arm-rockchip830-linux-uclibcgnueabihf.g 自己写的文件保存了,然后在自己文件这一栏右键打开终端然后生成可执行文件,但是它一直是生成的标准历程里的可执行文件 ``` +## 板卡使用 +当编译出来的文件在板卡上运行时,出现以下提示: +```markdown + +root@lockzhiner_vision_module:/root/shufflenet# ./Test-shufflenet shufflenet-backbone.rknn 1.JPEG + +/root/shufflenet/Test-shufflenet: symbol '_ZTVN24lockzhiner_vision_module6vision18ShuffleNetBackboneE': can't resolve symbol +root@lockzhiner_vision_module:/root/shufflenet# +``` +可以排查动态库是否是最新版本 + 请根据**自定义文件名**修改cmak,如果不知到如何修改的话,可以拷贝一个例程,不修改文件名,只修改里面的内容(同时如果需要视觉功能最好使用E或D例程进行替换可以减少写cmake的麻烦) \ No newline at end of file diff --git a/README.md b/README.md index ff82894e46e8fc1a40ae01f8fe6ac53eaef12ce2..e405f8c745c2bf04ce7809ce6dcd8ebb68a135ba 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,24 @@ > > * 更详细的参数请参考 [凌智视觉模块设备详细参数文档](./docs/device_parameters.md) +## 支持模型列表 +|模型名称|模型类型|备注| +|---|---|---| +|PicoDet|目标检测模型|| +|YoloV5|目标检测模型|| +|YoloV8|目标检测模型|| +|PicoClas|图像分类模型|| +|PP-HumanSeg|人像分割模型|执行速度慢,不满足实时要求| +|PFLD|人脸关键点检测模型|| +|LPRNet|车牌识别模型|| +|PP-OCRv3-Rec|文本识别模型|| +|PP-OCRv4-Det|文本检测模型|| +|CRNN-Lite|文本识别模型|| +|DB-Net|文本检测模型|| +|Retainface|人脸特征点提取模型|| +|blazehand_landmark|手部关键点提取模型|meidiapipe 手部关键点提取模型| + + ## 🔄 最新日志 ## 🎁 购买链接 @@ -106,6 +124,12 @@ Lockzhiner Vision Module 拥有丰富的 IO 接口,其接口图片如下图所 * [凌智视觉模块 PWM 使用指南](./example/periphery/pwm) * [凌智视觉模块 USART(串口) 使用指南](./example/periphery/usart) +### 🔌 无线图传案例 + +无线图传,全称“无线图像传输”,是指利用无线电波将视频图像信号从发送端(如摄像机、视觉模块)实时传输到接收端(如监视器、手机、PC),并在接收端还原显示的技术。简单来说,就是给摄像头“剪掉”视频线,换成无形的电磁波。为降低使用成本,在本模块中,我们提供了基于 socket 实现的无线图传功能示例。 + +* [凌智视觉模块无线图传案例](./example/periphery/wifi) + ## 🧠 深度学习视觉部署教程 这一部分教程将引导你从零开始训练并部署各种深度学习模型,包括分类模型、检测模型、分割模型和 OCR 模型。通过这些实战教程,你将学习到如何利用 Lockzhiner Vision Module 的高性能计算能力以及借助飞桨生态系统的丰富资源,轻松实现视觉任务的训练和部署。 diff --git a/docs/wifi/README.md b/docs/wifi/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f84cb5d2f6f0745eb8f07e17b8616cbd684edf28 --- /dev/null +++ b/docs/wifi/README.md @@ -0,0 +1,70 @@ +# 凌智视觉模块 Wi-Fi 使用指南 + +发布版本:V1.0 +日期:2026-07-21 +文件密级:■公开 + +--- + +## 免责声明 + +本文档按“现状”提供,福州凌睿智捷电子有限公司(以下简称“本公司”)不对本文档中的任何陈述、信息和内容的准确性、可靠性、完整性、适销性、适用性及非侵权性提供任何明示或暗示的声明或保证。本文档仅作为使用指导的参考。 + +由于产品版本升级或其他原因,本文档可能在未经任何通知的情况下不定期更新或修改。 + +## 读者对象 + +- 技术支持工程师 +- 软件开发工程师 + +## 修订记录 + +| 日期 | 版本 | 作者 | 修改说明 | +| ---------- | ---- | ------ | ------------ | +| 2026/07/21 | V1.0 | 钟海滨 | 初始正式版本 | + +--- + + +## 1 性能测试 + +为评估 Wi‑Fi 实际传输性能,推荐使用以下两种方法。测试时,开发板作为服务端或客户端均可。 + +### 1.1 FTP 文件传输(模拟真实业务) + +在理想环境下,单向 FTP 吞吐量可达 **30~40 Mbps**(约 4~5 MB/s)。若测速明显偏低,请检查: +- 天线是否牢固连接; +- 信道是否存在干扰; +- CPU 负载是否过高(可用 `top` 命令观察)。 + +### 1.2 iperf3 网络吞吐测试(压力测试) + +使用 iperf3 进行 TCP 吞吐量测试,结果如下(测试环境同第2节说明): + +| 测试条件 | 平均带宽(发送) | 平均带宽(接收) | 重传次数 | 瞬时峰值 | +| -------------------------------- | :--------------: | :--------------: | :------: | :------: | +| 单流默认(-t 60) | 13.2 Mbps | 13.1 Mbps | 0 | ~20 Mbps | +| 单流大块(-l 1M -t 30) | 18.9 Mbps | 18.5 Mbps | 6 | ~29 Mbps | +| **4 流并发(-P 4 -t 30)** | **26.9 Mbps** | **26.4 Mbps** | 0 | **37.0 Mbps** | +| 反向单流(-R -t 10) | ~13 Mbps | ~13 Mbps | 0 | ~15 Mbps | + +**结果解读**: +- 重传次数为 0(或极低)表明 Wi‑Fi 空口环境优良,无丢包和干扰。 +- 多流并发能有效利用 CPU 的中断处理能力,总带宽提升明显,瞬时峰值可达 37 Mbps,接近 FTP 实测水平。 +- 单流带宽受限主要源于 CPU 内存拷贝开销,而 FTP 利用零拷贝(`sendfile`)技术可绕过该瓶颈,故实际文件传输性能更高。 + +**综合结论**:该模块的 Wi‑Fi 硬件与驱动状态正常,具备稳定提供 30~40 Mbps 实际业务吞吐量的能力。iperf3 可作为链路质量的辅助验证工具,但最终应用性能应以 FTP 文件传输为准。 + +--- + +## 2 注意事项 + +1. **天线必接**:务必在首次上电前连接好天线,避免射频功放损坏。 +2. **配置保存**:修改配置文件后,请使用 `reboot` 命令正常重启,切勿直接断电。 +3. **性能验证**:实际使用中,请根据自身应用场景进行带宽测试,确保性能满足需求。 +4. **干扰规避**:2.4 GHz 频段易受蓝牙、微波炉等设备干扰,建议选择干扰较少的信道。 +5. **固件升级**:如遇连接异常,可检查固件版本,必要时联系技术支持获取更新。 + +--- + +*本文档最终解释权归福州凌睿智捷电子有限公司所有。* diff --git "a/docs/\346\235\277\345\215\241\344\277\241\346\201\257/cpuinfo.md" "b/docs/\346\235\277\345\215\241\344\277\241\346\201\257/cpuinfo.md" new file mode 100644 index 0000000000000000000000000000000000000000..61ad511f5e01054965c4b8a1d94066f744434de5 --- /dev/null +++ "b/docs/\346\235\277\345\215\241\344\277\241\346\201\257/cpuinfo.md" @@ -0,0 +1,38 @@ +# 命令:查看CPU信息 +```bash +cat /proc/cpuinfo +``` +# 返回信息 +```markdown + +root@lockzhiner_vision_module:/proc# cat /proc/cpuinfo +processor : 0 +model name : ARMv7 Processor rev 5 (v7l) +BogoMIPS : 69.82 +Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae +CPU implementer : 0x41 +CPU architecture: 7 +CPU variant : 0x0 +CPU part : 0xc07 +CPU revision : 5 + +Hardware : Rockchip (Device Tree) +Revision : 0000 +Serial : 85503951ea2d8830 + +``` +# 解读 +| 字段 | 当前值 | 含义 / 说明 | +|------|--------|-------------| +| **processor** | `0` | 当前系统识别的 CPU 核心编号(仅识别到 1 个核心,常见于单核 Cortex‑A7 或未开启其他核心) | +| **model name** | `ARMv7 Processor rev 5 (v7l)` | 32 位 ARMv7 架构,`v7l` 表示小端模式(Little‑Endian),修订版本为 r5 | +| **BogoMIPS** | `35.95` | 内核启动时计算的**粗略速度值**,仅用于调度校准,**并非实际主频** | +| **Features** | `half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae` | 支持的指令集与硬件特性:
• NEON / VFP:多媒体与浮点加速
• 硬件整数除法 (`idiva`, `idivt`)
• 大物理地址扩展 (`lpae`,支持 >4GB 内存寻址) | +| **CPU implementer** | `0x41` | 处理器厂商代码,`0x41` 代表 **ARM** 公司 | +| **CPU architecture** | `7` | ARM 架构版本号,即 ARMv7 | +| **CPU variant** | `0x0` | 处理器变体(通常用于标识芯片步进) | +| **CPU part** | `0xc07` | **核心型号编码**,`0xC07` 对应 **Cortex‑A7** | +| **CPU revision** | `5` | 该核心的修订版本号 | +| **Hardware** | `Rockchip (Device Tree)` | 硬件平台为 **瑞芯微(Rockchip)**,内核采用设备树(Device Tree)驱动 | +| **Revision** | `0000` | 板级硬件版本号(此处为默认值,可能未在 U‑Boot 中烧录具体版本) | +| **Serial** | `85503951ea2d8830` | SoC 芯片的**唯一硬件序列号**,常用于设备绑定、授权或加密 | diff --git a/example/periphery/wifi/README.md b/example/periphery/wifi/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3c03ca6418d5ed90beda3c4d8c5b7793ba0d53a4 --- /dev/null +++ b/example/periphery/wifi/README.md @@ -0,0 +1,127 @@ +# 凌智视觉模块 Wi-Fi 使用指南 + +发布版本:V1.0 +日期:2026-07-21 +文件密级:■公开 + +--- + +## 免责声明 + +本文档按“现状”提供,福州凌睿智捷电子有限公司(以下简称“本公司”)不对本文档中的任何陈述、信息和内容的准确性、可靠性、完整性、适销性、适用性及非侵权性提供任何明示或暗示的声明或保证。本文档仅作为使用指导的参考。 + +由于产品版本升级或其他原因,本文档可能在未经任何通知的情况下不定期更新或修改。 + +## 读者对象 + +- 技术支持工程师 +- 软件开发工程师 + +## 修订记录 + +| 日期 | 版本 | 作者 | 修改说明 | +| ---------- | ---- | ------ | ------------ | +| 2026/07/21 | V1.0 | 钟海滨 | 初始正式版本 | + +--- + +## 1 简介 +### 1.1 WIFI 模组概述 +Wi‑Fi 是一种用于连接计算机与外部设备并进行数据传输的无线通信技术。凌智视觉模块(Lockzhiner Vision Module)搭载了 Wi‑Fi 模组,支持 2.4 GHz 频段的无线网络连接。 + +本指南将指导您完成 Wi‑Fi 的连接配置,并通过 FTP 和 iperf3 工具验证无线性能。 + +wifi模组示意图 + +![](./images/wifi_module_1.jpg) +![](./images/wifi_module_2.jpg) +![](./images/wifi_module_3.jpg) + +在连接时请确保wifi模块连接引脚,同视觉模块相互对应。 + +![](../../../images/periphery.png) +--- +### 1.2 Wi‑Fi 系统特点 + +本模块搭载标准 Linux/RTOS 操作系统,内置全功能 TCP/IP 网络协议栈。开发时直接调用标准 BSD Socket API 进行网络编程(与 PC 端开发无异),无需依赖串口 AT 指令集中转数据。 + +相较于传统的“MCU主控 + ESP8266 AT固件”方案,本方案具有以下优势: + +- 开发便捷:直接使用 socket、select 等标准接口,无需拼接与解析 AT 命令字符串。 +- 高吞吐率:摄像头数据在模块内部通过系统总线直接交由 Wi-Fi 网卡发送,无串口速率瓶颈,可支撑 30fps 实时图传。 +- 灵活调优:可借助 iptables、tc 等 Linux 原生网络工具进行深度流量控制与性能优化。 + +## 2 设备参数 + +| 项目 | 参数 | +| -------------- | ----------------------------------- | +| Wi‑Fi 核心芯片 | RTL8723DS(或 RTL8723 系列) | +| 支持频段 | 2.4 GHz | +| 协议标准 | IEEE 802.11n(Wi‑Fi 4) | +| 最高连接速率 | 150 Mbps(40 MHz 频宽) | +| 实际吞吐量 | 约 30~40 Mbps(FTP 应用层测试) | + +> **说明**:150 Mbps 为物理层协商速率,实际有效载荷受协议开销、环境干扰、CPU 负载等因素影响,应用层速率在 30~40 Mbps 属正常范围。本文所有性能数据均在开发板与无线路由器直线距离 2 米、无遮挡、外接天线的环境下测得。 + +--- + +## 3 Wi‑Fi 连接配置方式 + +模块支持多种 Wi‑Fi 配置方式,按优先级从高到低排列如下: + + +❗ **注意事项**:使用 wifi 时需要烧录 wifi 版本的镜像,否则 wifi 模块无法使用。 + +### 3.1 U盘配置文件(优先级最高) + +1. 将格式化为 FAT32 的 U 盘插入模块 USB 接口。 +2. 模块会自动在 U 盘根目录生成 `LockzhinerVisionModule.cfg` 配置文件(若不存在)。 +3. 使用文本编辑器打开该文件,修改以下两项: + ``` + wifi_ssid=你的Wi‑Fi名称 + wifi_passwd=你的Wi‑Fi密码 + ``` +4. 保存文件并安全弹出 U 盘。 +5. 执行 `reboot` 命令复位模块(**切勿直接断电**,否则配置可能未保存)。 + +> 当 U 盘配置文件存在时,系统将优先使用其中的设置,忽略其他配置。 + +### 3.2 开机自动连接脚本 + +若未插入 U 盘或 U 盘中无配置文件,系统将使用内置脚本 `/etc/init.d/S51wifi_connect` 中的参数。 + +编辑该脚本,修改以下变量: +```bash +WIFISSID="你的Wi‑Fi名称" +WIFIPWD="你的Wi‑Fi密码" +TARGET_IP_ALIAS="172.32.0.144" # 固定IP别名,可选 +``` +保存后重启模块即可生效。 + +### 3.3 手动连接命令 + +在命令行终端中,可使用以下工具手动连接: +- **连接指定 Wi‑Fi**: + ```bash + wifi_start.sh "要连接的Wi‑Fi名" "密码" + ``` +- **查看当前连接信息**(信号强度、IP 地址等): + ```bash + wifi_signal.sh + ``` + +### 3.4 网络地址说明 + +- `wlan0` 接口默认通过 DHCP 获取动态 IP。 +- 系统同时为 `wlan0` 配置了固定 IP 别名 `172.32.0.144/16`,以便在 DHCP 分配地址未知时仍能访问模块。 +- 若需从电脑访问该别名地址,请确保电脑与模块在同一局域网,并在电脑的对应网卡 IPv4 高级设置中添加静态 IP:`172.32.0.1`,子网掩码 `255.255.0.0`。 + +--- + +## 4 图传测试例程 + +### 4.1 python 测试例程 + +[python 测试例程](./python)分为 lockai 部分和 接收部分,在使用时需先启动 lockai 部分,再启动接收部分。 + +*本文档最终解释权归福州凌睿智捷电子有限公司所有。* diff --git a/example/periphery/wifi/images/runtime.jpg b/example/periphery/wifi/images/runtime.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a1413c0f25564eeb7be864d93f8c30d2f4c5102 Binary files /dev/null and b/example/periphery/wifi/images/runtime.jpg differ diff --git a/example/periphery/wifi/images/simple_gui.jpg b/example/periphery/wifi/images/simple_gui.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0b06f18f251877d53f950c1465f6d29810b8f86a Binary files /dev/null and b/example/periphery/wifi/images/simple_gui.jpg differ diff --git a/example/periphery/wifi/images/wifi_module_1.jpg b/example/periphery/wifi/images/wifi_module_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a23d4feab7b315ab509ca3ce98a4c2eb50cac257 Binary files /dev/null and b/example/periphery/wifi/images/wifi_module_1.jpg differ diff --git a/example/periphery/wifi/images/wifi_module_2.jpg b/example/periphery/wifi/images/wifi_module_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e984de65fd1d4f7facf4742eb42ba47f4dc92aa0 Binary files /dev/null and b/example/periphery/wifi/images/wifi_module_2.jpg differ diff --git a/example/periphery/wifi/images/wifi_module_3.jpg b/example/periphery/wifi/images/wifi_module_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cedff48d4c09c3ac227408d5274317f530431fad Binary files /dev/null and b/example/periphery/wifi/images/wifi_module_3.jpg differ diff --git a/example/periphery/wifi/python/README.md b/example/periphery/wifi/python/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2d8930810c3a7f4288a983c88e7232f32b0babc6 --- /dev/null +++ b/example/periphery/wifi/python/README.md @@ -0,0 +1,154 @@ +# 凌智视觉模块无线图传测试例程 + +发布版本:V1.0 +日期:2026-07-22 +文件密级:■公开 + +--- + +## 免责声明 + +本文档按“现状”提供,福州凌睿智捷电子有限公司(以下简称“本公司”)不对本文档中的任何陈述、信息和内容的准确性、可靠性、完整性、适销性、适用性及非侵权性提供任何明示或暗示的声明或保证。本文档仅作为使用指导的参考。 + +由于产品版本升级或其他原因,本文档可能在未经任何通知的情况下不定期更新或修改。 + +## 读者对象 + +- 技术支持工程师 +- 软件开发工程师 + +## 修订记录 + +| 日期 | 版本 | 作者 | 修改说明 | +| ---------- | ---- | ------ | ------------ | +| 2026/07/22 | V1.0 | 钟海滨 | 初始正式版本 | + +--- + +## 1 简介 + +本测试例程用于测试凌智视觉模块的无线图传功能。通过TCP协议将摄像头采集的视频流实时传输到客户端,客户端接收并显示、保存视频流。该例程包含服务端(推流端)和客户端(接收端)两部分,采用纯Python实现,依赖OpenCV库,适用于嵌入式视觉模块或通用Linux/Windows平台。 + +**主要特性:** +- 服务端:持续采集摄像头图像,通过TCP发送JPEG压缩帧,支持帧率控制和发送缓冲机制。 +- 客户端:接收JPEG数据并解码,实时显示视频,同时保存为AVI文件,支持FPS统计。 +- 连接管理:服务端支持多客户端依次连接(单连接模式),自动检测客户端断开。 +- 精简通信:仅传输视频数据,无额外命令交互,降低开销。 + +--- + +## 2 系统架构 +```mermaid +sequenceDiagram + participant Server + participant Client + Server->>Server: 监听端口6810 + Client->>Server: TCP连接请求 + Server-->>Client: 连接建立 + loop 每帧 + Server->>Server: 读取摄像头帧 + Server->>Server: JPEG编码 + Server->>Client: 发送4字节长度头 + JPEG数据 + Client->>Client: 解码显示 + Client->>Client: 写入AVI文件 + end + Client->>Server: 断开连接(或超时) + Server->>Server: 关闭连接,等待下一客户端 +``` +### 2.1 服务端(推流端) + +- **语言**:Python 3 +- **依赖库**:`lockzhiner_vision_module.cv2`(基于OpenCV)、`threading`、`socket`、`select`、`time` +- **功能模块**: + - **主线程**:创建TCP服务端监听,接收客户端连接,启动发送与接收子线程。 + - **发送线程(`stream_sender`)**:以固定帧率(默认30fps)读取摄像头,使用JPEG编码(质量75),通过非阻塞select检查发送缓冲区,丢弃过载帧,确保实时性。 + - **接收线程(`client_receiver`)**:仅监测客户端连接状态,若客户端断开则设置停止事件,通知发送线程退出。 + +### 2.2 客户端(接收端) + +- **语言**:Python 3 +- **依赖库**:`opencv-python`、`numpy`、`socket`、`time` +- **功能模块**: + - 连接服务端后,循环接收每帧数据(4字节长度头 + JPEG数据)。 + - 解码为BGR图像,显示在窗口中,并写入本地AVI视频文件。 + - 实时计算并显示接收FPS,支持按`q`键退出。 + +### 2.3 通信协议 + +- **传输层**:TCP(IPv4) +- **帧格式**: + - 4字节大端整数表示JPEG数据长度(`data_length`) + - 紧接`data_length`字节的JPEG图像数据 +- **端口**:6810(可配置) +- **IP**:服务端绑定`192.168.1.62`(可根据实际网络修改) + +--- + +## 3 环境要求 + +### 3.1 硬件 +- 凌智视觉模块(或其他搭载摄像头且支持Python的嵌入式平台) +- PC(客户端)与模块处于同一局域网(Wi-Fi或以太网) + +### 3.2 软件 +- Python 3.6+ +- OpenCV(服务端使用`lockzhiner_vision_module.cv2`,客户端使用标准`cv2`) +- Numpy(客户端) + +### 3.3 网络配置 +- 服务端IP:`192.168.1.62`(示例,请根据实际修改) +- 客户端需能ping通服务端IP + +--- + +## 4 部署步骤 + +### 4.1 服务端部署(凌智视觉模块) + +1. 将服务端代码(`tcp_rv1106.py`)拷贝至模块。 +2. 确保模块已安装`lockzhiner_vision_module`库(含OpenCV)。 +3. 运行服务端: + ```bash + python tcp_rv1106.py + ``` + 输出示例: + ``` + Camera ready + Server listening on 192.168.1.62:6810 + ``` +4. 等待客户端连接。 + +### 4.2 客户端部署(PC) + +1. 安装Python依赖: + ```bash + pip install opencv-python numpy + ``` +2. 将客户端代码(`tcp_pc.py`)保存到PC。 +3. 若服务端IP不是`192.168.1.62`,需修改客户端中的`HOST`变量。 +4. 运行客户端: + ```bash + python tcp_pc.py + ``` + 连接成功后会弹出视频窗口,并开始录制。 + +### 4.3 停止与退出 +- 服务端:按`Ctrl+C`终止。 +- 客户端:在视频窗口按`q`键,或`Ctrl+C`。 + + +## 5 gui 界面 + +为了方便测试使用,提供了一个简单的 gui 界面版本,在 pc 上运行,只需修改服务器IP和端口号即可。 +![gui 界面](../images/simple_gui.jpg) + +运行时界面 + +![运行时界面](../images/runtime.jpg) + + +如果需要打包为可执行文件,可使用 `pyinstaller` 工具。 + +```bash +pyinstaller --onefile --windowed gui-test.py +``` diff --git a/example/periphery/wifi/python/code/gui-test.py b/example/periphery/wifi/python/code/gui-test.py new file mode 100644 index 0000000000000000000000000000000000000000..67fcf9f5e31bed456e2dbac1b438eb1b439e0fcf --- /dev/null +++ b/example/periphery/wifi/python/code/gui-test.py @@ -0,0 +1,174 @@ +import socket +import cv2 +import numpy as np +import time +import json +import os +import tkinter as tk +from tkinter import messagebox + +# ---------- 配置管理 ---------- +CONFIG_FILE = "client_config.json" + +def load_config(): + """加载配置文件,若不存在返回默认值""" + default = {"host": "192.168.1.62", "port": 6810} + if os.path.exists(CONFIG_FILE): + try: + with open(CONFIG_FILE, "r") as f: + cfg = json.load(f) + # 确保有host和port字段 + if "host" in cfg and "port" in cfg: + return cfg + except Exception: + pass + return default + +def save_config(host, port): + """保存配置到文件""" + with open(CONFIG_FILE, "w") as f: + json.dump({"host": host, "port": port}, f, indent=2) + +# ---------- 视频接收函数 ---------- +def receive_image(conn, timeout=5): + """从连接接收一帧JPEG图像,返回解码后的BGR图像或None""" + conn.settimeout(timeout) + try: + length_data = conn.recv(4) + if len(length_data) != 4: + return None + data_length = int.from_bytes(length_data, byteorder='big') + image_data = b'' + received = 0 + while received < data_length: + chunk = conn.recv(min(4096, data_length - received)) + if not chunk: + break + image_data += chunk + received += len(chunk) + if received != data_length: + return None + img_np = np.frombuffer(image_data, dtype=np.uint8) + frame = cv2.imdecode(img_np, cv2.IMREAD_COLOR) + return frame + except (socket.timeout, ConnectionError, OSError): + return None + +# ---------- 视频流主循环 ---------- +def start_stream(host, port): + """连接服务器并显示视频流,按q退出""" + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + try: + sock.connect((host, port)) + print(f"Connected to {host}:{port}") + except Exception as e: + messagebox.showerror("连接失败", f"无法连接到 {host}:{port}\n错误: {e}") + return + + fps_start = time.time() + fps_count = 0 + fps_display_interval = 1.0 + + print("Receiving video stream. Press 'q' in the display window to stop.") + try: + while True: + frame = receive_image(sock, timeout=2) + if frame is None: + print("No frame received (timeout or connection lost), retrying...") + time.sleep(0.5) + # 这里简单重试,若长时间无数据可退出 + continue + + # 更新FPS并显示 + fps_count += 1 + now = time.time() + if now - fps_start >= fps_display_interval: + fps = fps_count / (now - fps_start) + print(f"Current FPS: {fps:.2f}") + cv2.putText(frame, f"FPS: {fps:.2f}", (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + fps_start = now + fps_count = 0 + + cv2.imshow('Video Stream', frame) + key = cv2.waitKey(1) & 0xFF + if key == ord('q'): + print("'q' pressed, stopping...") + break + + except KeyboardInterrupt: + print("Interrupted by user") + except Exception as e: + print(f"Error during receiving: {e}") + finally: + cv2.destroyAllWindows() + sock.close() + print("Stream stopped.") + +# ---------- GUI 配置窗口 ---------- +def show_config_window(): + """显示配置窗口,返回 (host, port) 或 (None, None) 表示取消""" + cfg = load_config() + window = tk.Tk() + window.title("视频客户端配置") + window.geometry("300x150") + window.resizable(False, False) + + tk.Label(window, text="服务器IP:").grid(row=0, column=0, padx=10, pady=10, sticky='e') + entry_host = tk.Entry(window, width=20) + entry_host.grid(row=0, column=1, padx=10, pady=10) + entry_host.insert(0, cfg["host"]) + + tk.Label(window, text="端口:").grid(row=1, column=0, padx=10, pady=10, sticky='e') + entry_port = tk.Entry(window, width=20) + entry_port.grid(row=1, column=1, padx=10, pady=10) + entry_port.insert(0, str(cfg["port"])) + + result = {"host": None, "port": None, "cancelled": True} + + def on_connect(): + host = entry_host.get().strip() + port_str = entry_port.get().strip() + if not host: + messagebox.showerror("错误", "请输入服务器IP") + return + try: + port = int(port_str) + if port <= 0 or port > 65535: + raise ValueError + except ValueError: + messagebox.showerror("错误", "端口必须是1-65535的整数") + return + # 保存配置 + save_config(host, port) + result["host"] = host + result["port"] = port + result["cancelled"] = False + window.destroy() + + def on_cancel(): + result["cancelled"] = True + window.destroy() + + btn_frame = tk.Frame(window) + btn_frame.grid(row=2, column=0, columnspan=2, pady=10) + tk.Button(btn_frame, text="连接", command=on_connect, width=8).pack(side=tk.LEFT, padx=5) + tk.Button(btn_frame, text="取消", command=on_cancel, width=8).pack(side=tk.LEFT, padx=5) + + window.mainloop() + + if result["cancelled"]: + return None, None + return result["host"], result["port"] + +# ---------- 主函数 ---------- +def main(): + host, port = show_config_window() + if host is None or port is None: + print("用户取消,退出程序") + return + start_stream(host, port) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/periphery/wifi/python/code/tcp_pc.py b/example/periphery/wifi/python/code/tcp_pc.py new file mode 100644 index 0000000000000000000000000000000000000000..543a312df01d85577953472d97e7dc05e3072737 --- /dev/null +++ b/example/periphery/wifi/python/code/tcp_pc.py @@ -0,0 +1,117 @@ +import socket +import cv2 +import numpy as np +import time + +def receive_image(conn, timeout=5): + """从连接接收一帧JPEG图像,返回解码后的BGR图像或None""" + conn.settimeout(timeout) + try: + # 1. 读取4字节长度(大端) + length_data = conn.recv(4) + if len(length_data) != 4: + return None + data_length = int.from_bytes(length_data, byteorder='big') + # 2. 循环读取图像数据 + image_data = b'' + received = 0 + while received < data_length: + chunk = conn.recv(min(4096, data_length - received)) + if not chunk: + break + image_data += chunk + received += len(chunk) + if received != data_length: + return None + # 3. 解码为OpenCV图像 + img_np = np.frombuffer(image_data, dtype=np.uint8) + frame = cv2.imdecode(img_np, cv2.IMREAD_COLOR) + return frame + except (socket.timeout, ConnectionError, OSError): + return None + +def main(): + HOST = '192.168.1.62' + PORT = 6810 + + # 连接服务器 + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + try: + sock.connect((HOST, PORT)) + print(f"Connected to {HOST}:{PORT}") + except Exception as e: + print(f"Connection failed: {e}") + return + + # 准备视频保存 + timestamp = int(time.time() * 1000) + video_filename = f"video_stream_{timestamp}.avi" + fourcc = cv2.VideoWriter_fourcc(*'XVID') + video_writer = None + first_frame = True + + # FPS统计 + fps_start = time.time() + fps_count = 0 + fps_display_interval = 1.0 # 每秒更新一次FPS显示 + + print("Receiving video stream. Press 'q' in the display window to stop and save.") + + try: + while True: + frame = receive_image(sock, timeout=2) + if frame is None: + print("No frame received (timeout or connection lost), retrying...") + time.sleep(0.5) + # 尝试重新连接(简单处理:break并退出) + # 此处为演示,直接退出;实际可尝试重连 + break + + # 首次帧初始化VideoWriter + if first_frame: + height, width, _ = frame.shape + video_writer = cv2.VideoWriter(video_filename, fourcc, 20.0, (width, height)) + if not video_writer.isOpened(): + print("Failed to create video writer") + break + first_frame = False + print(f"Recording started, resolution {width}x{height}") + + # 更新FPS + fps_count += 1 + now = time.time() + if now - fps_start >= fps_display_interval: + fps = fps_count / (now - fps_start) + print(f"Current FPS: {fps:.2f}") + # 在图像上显示FPS(可选) + cv2.putText(frame, f"FPS: {fps:.2f}", (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + fps_start = now + fps_count = 0 + + # 显示并写入 + cv2.imshow('Video Stream', frame) + video_writer.write(frame) + + # 按q退出 + key = cv2.waitKey(1) & 0xFF + if key == ord('q'): + print("'q' pressed, stopping...") + break + + except KeyboardInterrupt: + print("Interrupted by user") + except Exception as e: + print(f"Error during receiving: {e}") + finally: + # 清理资源 + if video_writer is not None: + video_writer.release() + print(f"Video saved as {video_filename}") + cv2.destroyAllWindows() + sock.close() + print("Client stopped.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/periphery/wifi/python/code/tcp_rv1106.py b/example/periphery/wifi/python/code/tcp_rv1106.py new file mode 100644 index 0000000000000000000000000000000000000000..71843f3737429c11dfb8b233e5128c7a0bccb6c3 --- /dev/null +++ b/example/periphery/wifi/python/code/tcp_rv1106.py @@ -0,0 +1,151 @@ +import lockzhiner_vision_module.cv2 as cv2 +from threading import Thread, Event +import socket +import time +import select + +# ---------- 发送函数 ---------- +def send_frame(conn, frame, quality=75): + """压缩并发送一帧,返回是否成功""" + try: + ret, img_encode = cv2.imencode('.jpg', frame, quality) + if not ret: + return False + data = img_encode + # 发送长度(4字节大端) + 图像数据 + payload = len(data).to_bytes(4,byteorder='big') + data + conn.sendall(payload) + # conn.sendall(len(data).to_bytes(4, byteorder='big')) + # conn.sendall(data) + return True + except (ConnectionResetError, OSError, BrokenPipeError): + return False + +# ---------- 发送线程 ---------- +def stream_sender(conn, shutdown_event, cap): + """ + 纯推流线程,持续从摄像头读取并发送,不受外部命令控制。 + 内含帧率限制(默认30fps)和缓冲区拥塞丢弃机制。 + """ + FPS_TARGET = 30 + QUALITY = 75 # 质量85可平衡画质与大小,可根据需要调整 + frame_interval = 1.0 / FPS_TARGET + + frame_counter = 0 + start_time = time.time() + last_send_time = 0 + + while not shutdown_event.is_set(): + # 帧率控制:不到时间不发送 + now = time.time() + if now - last_send_time < frame_interval: + # 短暂休眠,避免忙等 + time.sleep(0.001) + continue + last_send_time = now + + # 读取一帧 + ret, frame = cap.read() + if not ret: + time.sleep(0.01) # 读取失败时短暂等待 + continue + + # 检查发送缓冲区是否可写(非阻塞检查,避免积压) + # 若缓冲区满,则丢弃该帧,不编码不发送 + _, wlist, _ = select.select([], [conn], [], 0.001) + if not wlist: + # 缓冲区满,丢帧,继续下一轮循环 + continue + + # 编码并发送 + if not send_frame(conn, frame, QUALITY): + # 发送失败(连接断开),通知退出 + shutdown_event.set() + break + + # 统计FPS + frame_counter += 1 + if now - start_time >= 1.0: + print(f"Stream FPS: {frame_counter / (now - start_time):.1f}") + frame_counter = 0 + start_time = now + +# ---------- 接收线程 ---------- +def client_receiver(conn, shutdown_event): + """ + 仅负责检测客户端是否断开,不处理任何命令。 + 若客户端主动关闭,则设置shutdown_event通知发送线程退出。 + """ + try: + while not shutdown_event.is_set(): + # 使用非阻塞方式检查数据,一旦收到数据则忽略(因为不再需要命令) + # 但为了检测断开,只需尝试recv(1),若返回空则断开 + try: + data = conn.recv(1024) + if not data: # 客户端关闭连接 + print("Client disconnected.") + break + # 若有数据,不做任何处理,继续循环 + except BlockingIOError: + # 非阻塞模式下无数据则继续 + time.sleep(0.01) + continue + except (ConnectionResetError, OSError): + print("Connection error.") + break + finally: + shutdown_event.set() # 通知发送线程退出 + +# ---------- 主服务器 ---------- +if __name__ == "__main__": + HOST = '192.168.1.62' + PORT = 6810 + + # 打开摄像头 + cap = cv2.VideoCapture() + if not cap.open(0): + print("Failed to open camera") + exit(1) + # 设置分辨率(可选) + cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) + print("Camera ready") + + # 创建事件 + shutdown_event = Event() + + # 创建TCP服务器 + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # 禁用Nagle + server_socket.bind((HOST, PORT)) + server_socket.listen(5) + print(f"Server listening on {HOST}:{PORT}") + + try: + while not shutdown_event.is_set(): + conn, addr = server_socket.accept() + conn.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 256 * 1024) + conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # 对客户端也设置 + print(f"New connection from {addr}") + snd_buf = conn.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) + print(f"Send buffer size: {snd_buf} bytes") + + # 启动发送和接收线程 + sender = Thread(target=stream_sender, args=(conn, shutdown_event, cap)) + receiver = Thread(target=client_receiver, args=(conn, shutdown_event)) + sender.start() + receiver.start() + + # 等待线程结束(连接断开后重新进入accept) + sender.join() + receiver.join() + conn.close() + print(f"Connection with {addr} closed, waiting for next client...") + except KeyboardInterrupt: + print("Server interrupted by user.") + finally: + shutdown_event.set() + cap.release() + server_socket.close() + print("Server stopped.") \ No newline at end of file diff --git a/example/vision/detetcion/python/test_detection.py b/example/vision/detetcion/python/test_detection.py index 6ee2b9215273747d8f4741cb272737d02b14e386..dead3d5775840a04628ec937265a98946dd02c10 100644 --- a/example/vision/detetcion/python/test_detection.py +++ b/example/vision/detetcion/python/test_detection.py @@ -1,4 +1,5 @@ from lockzhiner_vision_module.cv2 import VideoCapture +import lockzhiner_vision_module.cv2 as cv2 from lockzhiner_vision_module.vision import PaddleDet, visualize from lockzhiner_vision_module.edit import Edit import time @@ -9,7 +10,7 @@ if __name__ == "__main__": if len(args) != 2: print("Need model path. Example: python test_detection.py LZ-Picodet.rknn") exit(1) - + edit = Edit() edit.start_and_accept_connection() @@ -45,9 +46,11 @@ if __name__ == "__main__": box = result.box score = result.score label_id = result.label_id + cv2.imwrite("result.jpg", cv2.cropImage(mat, result.box.x, result.box.y, result.box.width, result.box.height)) + print( f"(x,y,w,h,score,label_id): [{box.x},{box.y},{box.width},{box.height},{score},{label_id}]" ) vis_mat = visualize(mat, results) edit.print(vis_mat) - print(f"FPS is {1.0 / (total_time_ms/read_index)}") \ No newline at end of file + print(f"FPS is {1.0 / (total_time_ms/read_index)}") diff --git a/example/vision/meidiapipe/handlandmark/python/test_hand_landmark.py b/example/vision/meidiapipe/handlandmark/python/test_hand_landmark.py new file mode 100644 index 0000000000000000000000000000000000000000..1be67f981b57d57e8526b4d9d696b0473562bafc --- /dev/null +++ b/example/vision/meidiapipe/handlandmark/python/test_hand_landmark.py @@ -0,0 +1,46 @@ +from lockzhiner_vision_module.vision import HandLandmark, visualize +import lockzhiner_vision_module.cv2 as cv2 +import time +import sys + +NUM_HAND_LANDMARKS = 21 +LANDMARK_DIMS = 3 + +if __name__ == "__main__": + args = sys.argv + if len(args) != 3: + print("Usage: python test_hand_landmark.py ") + exit(1) + + model = HandLandmark() + if not model.initialize(args[1]): + print("Model initialization failed.") + exit(1) + + img = cv2.imread(args[2]) + if img is None: + print("Failed to read image.") + exit(1) + + t1 = time.time() + result = model.predict(img) + t2 = time.time() + elapsed = t2 - t1 + + print(f"Inference time: {elapsed:.4f}s") + print(f"Presence score: {result.presence_score}") + print(f"Handedness: {result.handedness} (0 = left, 1 = right)") + + if result.presence_score > 0.5: + print("\nHand landmarks:") + for i in range(NUM_HAND_LANDMARKS): + x = result.landmarks[i * 3] + y = result.landmarks[i * 3 + 1] + z = result.landmarks[i * 3 + 2] + print(f"Landmark {i}: ({x:.4f}, {y:.4f}, {z:.4f})") + + output = visualize(img, result) + cv2.imwrite("hand_landmark_result.jpg", output) + print("\nResult image saved as: hand_landmark_result.jpg") + else: + print("No hand detected (presence score too low)") \ No newline at end of file