[置顶] 泰晓 RISC-V 实验箱,配套 30+ 讲嵌入式 Linux 系统开发公开课
RISC-V AI 开发:用 D1 进行图片采集和人体识别
Corrector: TinyCorrect v0.1-rc3 - [autocorrect epw] Author: Jinwen Zhou zhoujwtony@163.com Date: 2022/08/04 Revisor: Falcon falcon@tinylab.org Project: RISC-V Linux 内核剖析 Sponsor: PLCT Lab, ISCAS
本文介绍在 D1 开发板上实现摄像头实时人体识别功能的过程。首先使能摄像头拍照的功能,接着使能深度学习框架 ncnn,最后结合两部分内容实现实时识别人体功能。
摄像头模块使能
开发板系统选择
如本系列第一部分所述,选择 D1-H 官方的固件 20210804(开机 HDMI 有小企鹅启动 logo):固件下载地址,此固件经测试可满足后续实验要求。
硬件准备
D1-H 开发板、一根 USB 线,一根串口线,一个 USB 摄像头。
软件准备
在运行本例之前,已经成功运行 Hello Word 程序。确保交叉编译工具和 ADB 工具都可正常使用。
下载 USB Camera demo 代码包:D1-H USB camera demo source code,并保存为 cam.c。
硬件连接
首先,请确保连接好需要用的硬件设备:
开发板开机,插入 USB 摄像头后,系统会自动识别并打印 USB 摄像头连接的信息,查看 /dev
外设目录:
$ ls /dev/video*
可以发现有 /dev/video0
和 /dev/video1
设备,video1
为 video0
的映射。
程序编译
通过上一篇配置好的交叉编译工具,可以直接编译如下:
riscv64-unknown-linux-gnu-gcc cam.c -o cam
文件上传
在 Windows 中使用 ADB 工具将其送入开发板中:
adb push cam ./.
程序运行
chmod +x test
./cam
此时程序运行过程中会打印一些内容,我们使用 Ctrl+C 终止程序运行后,可在当前文件夹看到名为 1.jpeg 图片,然后可以用 adb pull
导出来查看。
adb pull /root/1.jpeg .
Pull 成功,这样就可以直接在 Windows 下查看图片了,效果如下:
D1 上 ncnn 框架使能
ncnn 是腾讯开源的神经网络推理框架,具有轻量级,支持嵌入式平台的特点。它支持深度学习模型 caffe、mxnet、keras、pytorch(onnx)、darknet、tensorflow(mlir) 等框架。它兼容 RISC-V 架构,官方对该框架在 D1-H 上做了适配。故选用此框架进行相关实践。
编译工具下载
去平头哥芯片开放社区下载 工具链-900 系列。
下载后,进行解压,并设置环境变量:
tar -zxvf Xuantie-900-gcc-linux-5.10.4-glibc-x86_64-V2.2.6-20220516.tar.gz
export RISCV_ROOT_PATH=/home/nihui/osd/Xuantie-900-gcc-linux-5.10.4-glibc-x86_64-V2.2.6
根据官方说明文档,需要打开 $RISCV_ROOT_PATH/lib/gcc/riscv64-unknown-linux-gnu/10.2.0/include/riscv_vector.h
,在文件末尾,可以找到三个 #endif
,在文件里添加:
#endif
#define vfrec7_v_f32m1(x, vl) vfrdiv_vf_f32m1(x, 1.f, vl)
#define vfrec7_v_f32m2(x, vl) vfrdiv_vf_f32m2(x, 1.f, vl)
#define vfrec7_v_f32m4(x, vl) vfrdiv_vf_f32m4(x, 1.f, vl)
#define vfrec7_v_f32m8(x, vl) vfrdiv_vf_f32m8(x, 1.f, vl)
#define vfrec7_v_f16m1(x, vl) vfrdiv_vf_f16m1(x, 1.f, vl)
#define vfrec7_v_f16m2(x, vl) vfrdiv_vf_f16m2(x, 1.f, vl)
#define vfrec7_v_f16m4(x, vl) vfrdiv_vf_f16m4(x, 1.f, vl)
#define vfrec7_v_f16m8(x, vl) vfrdiv_vf_f16m8(x, 1.f, vl)
#define vfrsqrt7_v_f32m1(x, vl) vfrdiv_vf_f32m1(vfsqrt_v_f32m1(x, vl), 1.f, vl)
#define vfrsqrt7_v_f32m2(x, vl) vfrdiv_vf_f32m2(vfsqrt_v_f32m2(x, vl), 1.f, vl)
#define vfrsqrt7_v_f32m4(x, vl) vfrdiv_vf_f32m4(vfsqrt_v_f32m4(x, vl), 1.f, vl)
#define vfrsqrt7_v_f32m8(x, vl) vfrdiv_vf_f32m8(vfsqrt_v_f32m8(x, vl), 1.f, vl)
#define vfrsqrt7_v_f16m1(x, vl) vfrdiv_vf_f16m1(vfsqrt_v_f16m1(x, vl), 1.f, vl)
#define vfrsqrt7_v_f16m2(x, vl) vfrdiv_vf_f16m2(vfsqrt_v_f16m2(x, vl), 1.f, vl)
#define vfrsqrt7_v_f16m4(x, vl) vfrdiv_vf_f16m4(vfsqrt_v_f16m4(x, vl), 1.f, vl)
#define vfrsqrt7_v_f16m8(x, vl) vfrdiv_vf_f16m8(vfsqrt_v_f16m8(x, vl), 1.f, vl)
#endif
#endif
ncnn 下载和编译
git clone https://github.com/Tencent/ncnn.git
cd ncnn
mkdir build-c906
cd build-c906
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchains/c906-v226.toolchain.cmake \
-DCMAKE_BUILD_TYPE=release -DNCNN_OPENMP=OFF -DNCNN_THREADS=OFF -DNCNN_RUNTIME_CPU=OFF -DNCNN_RVV=ON \
-DNCNN_SIMPLEOCV=ON -DNCNN_BUILD_EXAMPLES=ON ..
cmake --build .
cmake --build . --target install
测试 example
这里准备实现对图片上的物体进行检测,ncnn 支持大部分常用的 CNN 网络,这里选择测试 nanodet 模型,这是一个可在移动设备上进行实时超快速高精度物体检测的模型。需要的文件有:
- ncnn/build-c906/examples/nanodet
- 测试图片 test.jpg
- nanodet 模型文件,找到
nanodet
对应的.bin
和.param
文件,它们分别为识别所需网络和训练过的参数。
将上述文件通过 ADB 工具下载到 D1-H 上,然后在开发板上执行:
./nanodet test.jpg
输出结果会保存在 image.png 中。
把 image.png 下载到本地查看,就可以看到检测结果。
摄像头人体实时检测功能实现
接下来将综合前两个部分的功能,实现摄像头定时的对出现的画面进行人体检测。每隔一秒钟进行拍摄一张图片,交给 ncnn 模块进行识别。
此功能即可为后续视频监控提供上传数据的信号,又可避免直接对视频使用深度学习检测带来的开销,适合在嵌入式平台上使用。
修改 ncnn 模块的例程
接着对例程中 nanodet.c 进行修改,使其成为独立的模块,该模块收到处理图片请求后对图片进行检测,并返回结果。
我们将采用管道通信的方式,让本模块作为独立的服务端程序,收到请求的第一个字节为功能号,1 代表人体识别功能。返回的数据第二个字节为识别结果,1 代表图片中有人,0 代表没有人。本模块目前只判断是否有人,后续只需增加与之通信的协议并增加相关实现便可对识别模块进行功能拓展。
打开 ncnn/examples/nanodet.cpp
文件,新增一个函数,根据容器 objects 中的 label
确定是否有人,该 objects 存储了识别结果。
/* 新增 is_person 函数 */
static int is_person(const std::vector<Object>& objects)
{
int ret = 0;
/* 图片中可识别目标存在 objects 容器中 */
for (size_t i = 0; i < objects.size(); i++)
{
const Object& obj = objects[i];
/* 目标结构体中 label 为 0 代表目标为人 */
if (obj.label == 0) {
ret = 1;
break;
}
}
return ret;
}
#define FIFO_1 "/tmp/1"
#define FIFO_2 "/tmp/2"
int main(int argc, char** argv)
{
char buffer[80];
int fd_w;
int fd_r;
int ret;
int datalen;
unlink(FIFO_1);
mkfifo(FIFO_1,0666);
unlink(FIFO_2);
mkfifo(FIFO_2,0666);// 建立有名管道
fd_w = open(FIFO_1,O_WRONLY);
fd_r = open(FIFO_2,O_RDONLY);
while(1)
{
memset(buffer,0,80);
/* 收取客户端发来的识别请求 */
datalen = read(fd_r,buffer,80);
if (datalen == -1)
continue;
if (buffer[0] == 1)
{
//start detect
if (argc != 2)
{
fprintf(stderr, "Usage: %s [imagepath]\n", argv[0]);
return -1;
}
/* 摄像头模块采集图片的保存路径 */
const char* imagepath = argv[1];
cv::Mat m = cv::imread(imagepath, 1);
if (m.empty())
{
fprintf(stderr, "cv::imread %s failed\n", imagepath);
return -1;
}
std::vector<Object> objects;
/* 使用 nanodet 框架进行识别,识别结果保存在 objects 容器中 */
detect_nanodet(m, objects);
ret = is_person(objects);
buffer[1] = ret;
write(fd_w,buffer,80);
}
}
return 0;
}
修改 camera 模块例程
修改摄像头模块代码 cam.c
,让其在保存照片后通知 ncnn 模块,并等待检测结果:
int main(int argc, char* argv[])
{
char buffer[80];
int fd_w;
int fd_r;
int datalen;
fd_r = open(FIFO_1,O_RDONLY);
fd_w = open(FIFO_2,O_WRONLY);
v4l2_init();
while(1)
{
v4l2Grab();
buffer[0] = 1;
/* 通知 ncnn 模块已经采集到图片并存储 */
datalen = write(fd_w,buffer,80);
if(datalen == -1)
continue;
/* 等待 ncnn 发回来的识别结果 */
if(read(fd_r,buffer,80))
{
if(buffer[1] == 1)
printf("person!\n");
else
printf("no person!\n");
}
sleep(1);
}
v4l2_close();
return 0;
}
编译
首先进入 ncnn/build-c906/
路径,输入如下命令编译 ncnn 检测程序 nanodet.cpp
文件:
make nanodet
编译成功,显示:
[ 0%] Built target ncnn-generate-spirv
[100%] Built target ncnn
[100%] Built target nanodet
接着使用 D1 SDK 源码中给的编译器编译 cam.c 文件:
riscv64-unknown-linux-gnu-gcc cam.c -o cam
编译完成后,准备运行的文件,放到一个文件夹 demo 里:
ncnn/build-c906/examples
路径下的nanodet
nanodet
模型文件,找到nanodet
对应的.bin
和.param
文件cam.c
的编译文件cam
将上述文件通过 ADB 工具发送到 D1-H 上,发送命令:
adb push <本机路径/demo> <开发板目标路径>
传输成功后,在开发板上找到 demo 文件夹,要实现两个进程之间的通信,首先在该路径下执行 nanodet
,加 &
让他后台运行,如果没有权限,首先输入 chmod
命令:
chmod +x nanodet
./nanodet 1.jpeg &
然后执行 cam:
chmod +x cam
./cam
运行成功的情况下,当摄像头中出现人时,会检测到有人,输出 person!
小结
本文简单实现了对摄像头采集图片的一个实时检测,后续可对识别模块进行优化拓展。
参考资料
猜你喜欢:
- 我要投稿:发表原创技术文章,收获福利、挚友与行业影响力
- 知识星球:独家 Linux 实战经验与技巧,订阅「Linux知识星球」
- 视频频道:泰晓学院,B 站,发布各类 Linux 视频课
- 开源小店:欢迎光临泰晓科技自营店,购物支持泰晓原创
- 技术交流:Linux 用户技术交流微信群,联系微信号:tinylab
支付宝打赏 ¥9.68元 | 微信打赏 ¥9.68元 | |
请作者喝杯咖啡吧 |
Read Album:
- TinyBPT 和面向 buildroot 的二进制包管理服务(3):服务端说明
- TinyBPT 和面向 buildroot 的二进制包管理服务(2):客户端说明
- TinyBPT 和面向 buildroot 的二进制包管理服务(1):设计简介与框架
- RISC-V Linux 内核及周边技术动态第 118 期
- RISC-V Linux 内核及周边技术动态第 117 期