[i=s] 本帖最后由 qhsj 于 2025-3-28 22:18 编辑 [/i]
0x01 简介
上一篇:【安信可小安派BW21-CBV-Kit】开箱与Arduino环境搭建
既然 BW21 能够本地运行 AI 模型,如人脸识别,但是推理一张图像甚至是视频流需要大量的计算处理,导致 MCU 发热,功耗较大;而 Rd-04 雷达能够检测周围物体运动,并且功耗较低,但不能检测人体。结合两者特性,我利用 BW21-CBV-Kit 开发板和 Rd-04 雷达模组 DIY 一个低功耗双模态人体检测装置
0x02 硬件连接

0x03 软件部分
0b0001 IIC 设置 Rd-04 初始化
#include "Wire.h"
#define RD04_ADDRESS 0x71
void IIC_WriteData(uint8_t reg_addr, uint8_t Buff) {
Wire.beginTransmission(RD04_ADDRESS);
Wire.write(reg_addr);
Wire.write(Buff);
int result = Wire.endTransmission();
if (result != 0) {
Serial.print("Error code: ");
Serial.println(result);
}
}
uint8_t IIC_ReadData(uint8_t reg_addr) { // 有问题读不了,待改进
Wire.beginTransmission(RD04_ADDRESS);
Wire.write(reg_addr);
Wire.endTransmission(false);
Wire.requestFrom(RD04_ADDRESS, 1);
if (Wire.available())
{
return Wire.read();
}
return 0;
}
void Rd04_Init() {
int i;
//uint8_t value;
Serial.println("\nRd-04 Init...");
for (i = 0; i < 5; i++) {
IIC_WriteData(0x13, 0x9B);
delay(1);
/*
value = IIC_ReadData(0x13);
Serial.print("READ VALUE = 0x");
if (value < 16) {
Serial.print("0");
}
Serial.print(value, HEX);
Serial.println("\n");
*/
}
IIC_WriteData(0x24, 0x03);
IIC_WriteData(0x04, 0x20);
IIC_WriteData(0x10, 0x20);
IIC_WriteData(0x03, 0x40);
IIC_WriteData(0x1C, 0x21);
// IIC_WriteData(0x11, 0x10);
IIC_WriteData(0x18, 0x6a);
IIC_WriteData(0x19, 0x00);
IIC_WriteData(0x1A, 0x55);
IIC_WriteData(0x1B, 0x01);
IIC_WriteData(0x1D, 0x80);
IIC_WriteData(0x1E, 0x0C);
IIC_WriteData(0x1F, 0x00);
IIC_WriteData(0x20, 0x00);
IIC_WriteData(0x21, 0x7D);
IIC_WriteData(0x22, 0x00);
IIC_WriteData(0x23, 0x0C);
delay(3000);
Serial.println("\nRd-04 Finished");
}
void setup()
{
......
// Rd-04 Init
Rd04_Init(); // 配置一次即可,后面可以注释
......
}
在使用 BW21 用 Wire.h
库 IIC 写入从机设备地址和寄存器地址后,想切换读取请求读取不了从机寄存器的数据,感觉代码也没啥问题...
感觉也没必要读取,应该也是读取为了验证是否写入,那我就把读取部分注释掉了

0b0010 读取雷达输出
#define USER_LED 24 // AMB_D24 PE_6 Green LED
#define RD04_ADDRESS 0x71 // Rd-04 雷达 iic 从机地址
#define RD04_OUTPUT_PIN 0 // 读取雷达输出引脚
int NN_Status = 1; // 1 开启人脸识别;0 关闭
int Rd04_count0 = 0; // 无人时计数器
int Rd04_count1 = 0; // 有人时计数器
int count = 0;
void setup()
{
......
pinMode(USER_LED, OUTPUT);
digitalWrite(USER_LED, HIGH);
pinMode(RD04_OUTPUT_PIN, INPUT_PULLDOWN);
......
}
void loop()
{
......
if (digitalRead(RD04_OUTPUT_PIN)) { // 1 雷达捕获到人体运动
Rd04_count1 ++;
Rd04_count0 = 0;
if (Rd04_count1 >= 5 && NN_Status == 0) {
facerecog.begin();
videoStreamerRGBFD.registerInput(Camera.getStream(CHANNELNN));
videoStreamerRGBFD.setStackSize();
videoStreamerRGBFD.setTaskPriority();
videoStreamerRGBFD.registerOutput(facerecog);
if (videoStreamerRGBFD.begin() != 0) {
Serial.println("StreamIO link start failed");
}
Camera.channelBegin(CHANNELNN);
NN_Status = 1;
digitalWrite(USER_LED, HIGH);
}
} else { // 0 雷达没有捕获到人体运动
Rd04_count0 ++;
Rd04_count1 = 0;
if (Rd04_count0 >= 40 && NN_Status == 1) { // 4秒内雷达无捕获人体运动则取消人脸识别推理
facerecog.end();
Camera.channelEnd(CHANNELNN);
NN_Status = 0;
digitalWrite(USER_LED, LOW);
}
}
delay(100);
if (count >= 20) {
OSD.createBitmap(CHANNEL);
OSD.update(CHANNEL);
count = 0;
}
count++;
}
主循环读雷达输出电平,当过久为低电平,即没有检测物体运动时,则关闭 AI 模型推理,并且关闭绿灯;当有检测物体运动时,则开启 AI 模型推理进行人脸识别。因此可以有效减少功耗,甚至可以再配置睡眠模式。
0b0011 人脸识别
基于 Arduino 例程修改的,参考教程:https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45738
在 “文件” -> “示例” -> “AmebaNN” -> “RTSPFaceRecognition” 中打开人脸识别示例

仅修改了尺寸 720 * 480
以及热点账号密码
0b0100 完整代码
/*
Example guide:
https://www.amebaiot.com/en/amebapro2-arduino-neuralnework-face-recognition/
Face registration commands
--------------------------
Point the camera at a target face and enter the following commands into the serial monitor,
Register face: "REG={Name}" Ensure that there is only one face detected in frame
Remove face: "DEL={Name}" Remove a registered face
Reset registered faces: "RESET" Forget all previously registered faces
Backup registered faces to flash: "BACKUP" Save registered faces to flash
Restore registered faces from flash: "RESTORE" Load registered faces from flash
NN Model Selection
-------------------
Select Neural Network(NN) task and models using modelSelect(nntask, objdetmodel, facedetmodel, facerecogmodel).
Replace with NA_MODEL if they are not necessary for your selected NN Task.
NN task
=======
OBJECT_DETECTION/ FACE_DETECTION/ FACE_RECOGNITION
Models
=======
YOLOv3 model DEFAULT_YOLOV3TINY / CUSTOMIZED_YOLOV3TINY
YOLOv4 model DEFAULT_YOLOV4TINY / CUSTOMIZED_YOLOV4TINY
YOLOv7 model DEFAULT_YOLOV7TINY / CUSTOMIZED_YOLOV7TINY
SCRFD model DEFAULT_SCRFD / CUSTOMIZED_SCRFD
MobileFaceNet model DEFAULT_MOBILEFACENET/ CUSTOMIZED_MOBILEFACENET
No model NA_MODEL
*/
#include "WiFi.h"
#include "StreamIO.h"
#include "VideoStream.h"
#include "RTSP.h"
#include "NNFaceDetectionRecognition.h"
#include "VideoStreamOverlay.h"
#include "Wire.h"
#define CHANNEL 0
#define CHANNELNN 3
// Customised resolution for NN
#define NNWIDTH 576
#define NNHEIGHT 320
VideoSetting config(VIDEO_FHD, 30, VIDEO_H264, 0);
VideoSetting configNN(NNWIDTH, NNHEIGHT, 10, VIDEO_RGB, 0);
NNFaceDetectionRecognition facerecog;
RTSP rtsp;
StreamIO videoStreamer(1, 1);
StreamIO videoStreamerFDFR(1, 1);
StreamIO videoStreamerRGBFD(1, 1);
char ssid[] = "your network SSID"; // your network SSID (name)
char pass[] = "your network password"; // your network password
int status = WL_IDLE_STATUS;
IPAddress ip;
int rtsp_portnum;
#define USER_LED 24 // AMB_D24 PE_6 Green LED
// Rd-04
#define RD04_ADDRESS 0x71 // Rd-04 雷达 iic 从机地址
#define RD04_OUTPUT_PIN 0 // 读取雷达输出引脚
int NN_Status = 1; // 1 开启人脸识别;0 关闭
int Rd04_count0 = 0; // 无人时计数器
int Rd04_count1 = 0; // 有人时计数器
int count = 0;
void IIC_WriteData(uint8_t reg_addr, uint8_t Buff) {
Wire.beginTransmission(RD04_ADDRESS);
Wire.write(reg_addr);
Wire.write(Buff);
int result = Wire.endTransmission();
if (result != 0) {
Serial.print("Error code: ");
Serial.println(result);
}
}
uint8_t IIC_ReadData(uint8_t reg_addr) { // 有问题读不了,待改进
Wire.beginTransmission(RD04_ADDRESS);
Wire.write(reg_addr);
Wire.endTransmission(false);
Wire.requestFrom(RD04_ADDRESS, 1);
if (Wire.available())
{
return Wire.read();
}
return 0;
}
void Rd04_Init() {
int i;
//uint8_t value;
Serial.println("\nRd-04 Init...");
for (i = 0; i < 5; i++) {
IIC_WriteData(0x13, 0x9B);
delay(1);
/*
value = IIC_ReadData(0x13);
Serial.print("READ VALUE = 0x");
if (value < 16) {
Serial.print("0");
}
Serial.print(value, HEX);
Serial.println("\n");
*/
}
IIC_WriteData(0x24, 0x03);
IIC_WriteData(0x04, 0x20);
IIC_WriteData(0x10, 0x20);
IIC_WriteData(0x03, 0x40);
IIC_WriteData(0x1C, 0x21);
// IIC_WriteData(0x11, 0x10);
IIC_WriteData(0x18, 0x6a);
IIC_WriteData(0x19, 0x00);
IIC_WriteData(0x1A, 0x55);
IIC_WriteData(0x1B, 0x01);
IIC_WriteData(0x1D, 0x80);
IIC_WriteData(0x1E, 0x0C);
IIC_WriteData(0x1F, 0x00);
IIC_WriteData(0x20, 0x00);
IIC_WriteData(0x21, 0x7D);
IIC_WriteData(0x22, 0x00);
IIC_WriteData(0x23, 0x0C);
delay(3000);
Serial.println("\nRd-04 Finished");
}
void setup()
{
Serial.begin(115200);
// Rd-04 Init
//Rd04_Init(); // 配置一次即可
pinMode(USER_LED, OUTPUT); // 配置用户绿灯引脚为输出模式
digitalWrite(USER_LED, HIGH); // 绿灯亮
pinMode(RD04_OUTPUT_PIN, INPUT_PULLDOWN); // 配置引脚 0 为读取模式
// Attempt to connect to Wifi network:
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
status = WiFi.begin(ssid, pass);
// wait 2 seconds for connection:
delay(2000);
}
ip = WiFi.localIP();
// Configure camera video channels with video format information
// Adjust the bitrate based on your WiFi network quality
config.setBitrate(2 * 720 * 480); // Recommend to use 2Mbps for RTSP streaming to prevent network congestion
Camera.configVideoChannel(CHANNEL, config);
Camera.configVideoChannel(CHANNELNN, configNN);
Camera.videoInit();
// Configure RTSP with corresponding video format information
rtsp.configVideo(config);
rtsp.begin();
rtsp_portnum = rtsp.getPort();
// Configure Face Recognition model
// Select Neural Network(NN) task and models
facerecog.configVideo(configNN);
facerecog.modelSelect(FACE_RECOGNITION, NA_MODEL, DEFAULT_SCRFD, DEFAULT_MOBILEFACENET);
facerecog.begin();
facerecog.setResultCallback(FRPostProcess);
// Configure StreamIO object to stream data from video channel to RTSP
videoStreamer.registerInput(Camera.getStream(CHANNEL));
videoStreamer.registerOutput(rtsp);
if (videoStreamer.begin() != 0) {
Serial.println("StreamIO link start failed");
}
// Start data stream from video channel
Camera.channelBegin(CHANNEL);
// Configure StreamIO object to stream data from RGB video channel to face detection
videoStreamerRGBFD.registerInput(Camera.getStream(CHANNELNN));
videoStreamerRGBFD.setStackSize();
videoStreamerRGBFD.setTaskPriority();
videoStreamerRGBFD.registerOutput(facerecog);
if (videoStreamerRGBFD.begin() != 0) {
Serial.println("StreamIO link start failed");
}
// Start video channel for NN
Camera.channelBegin(CHANNELNN);
// Start OSD drawing on RTSP video channel
OSD.configVideo(CHANNEL, config);
OSD.begin();
}
void loop()
{
if (Serial.available() > 0) {
String input = Serial.readString();
input.trim();
if (input.startsWith(String("REG="))) {
String name = input.substring(4);
facerecog.registerFace(name);
} else if (input.startsWith(String("DEL="))) {
String name = input.substring(4);
facerecog.removeFace(name);
} else if (input.startsWith(String("RESET"))) {
facerecog.resetRegisteredFace();
} else if (input.startsWith(String("BACKUP"))) {
facerecog.backupRegisteredFace();
} else if (input.startsWith(String("RESTORE"))) {
facerecog.restoreRegisteredFace();
}
}
if (digitalRead(RD04_OUTPUT_PIN)) { // 1 雷达捕获到人体运动
Rd04_count1 ++;
Rd04_count0 = 0;
if (Rd04_count1 >= 5 && NN_Status == 0) {
facerecog.begin();
videoStreamerRGBFD.registerInput(Camera.getStream(CHANNELNN));
videoStreamerRGBFD.setStackSize();
videoStreamerRGBFD.setTaskPriority();
videoStreamerRGBFD.registerOutput(facerecog);
if (videoStreamerRGBFD.begin() != 0) {
Serial.println("StreamIO link start failed");
}
Camera.channelBegin(CHANNELNN);
NN_Status = 1;
digitalWrite(USER_LED, HIGH);
}
} else { // 0 雷达没有捕获到人体运动
Rd04_count0 ++;
Rd04_count1 = 0;
if (Rd04_count0 >= 40 && NN_Status == 1) { // 4秒内雷达无捕获人体运动则取消人脸识别推理
facerecog.end();
Camera.channelEnd(CHANNELNN);
NN_Status = 0;
digitalWrite(USER_LED, LOW);
}
}
delay(100);
if (count >= 20) {
OSD.createBitmap(CHANNEL);
OSD.update(CHANNEL);
count = 0;
}
count++;
}
// User callback function for post processing of face recognition results
void FRPostProcess(std::vector<FaceRecognitionResult> results)
{
uint16_t im_h = config.height();
uint16_t im_w = config.width();
Serial.print("Network URL for RTSP Streaming: ");
Serial.print("rtsp://");
Serial.print(ip);
Serial.print(":");
Serial.println(rtsp_portnum);
Serial.println(" ");
printf("Total number of faces detected = %d\r\n", facerecog.getResultCount());
OSD.createBitmap(CHANNEL);
if (facerecog.getResultCount() > 0) {
for (int i = 0; i < facerecog.getResultCount(); i++) {
FaceRecognitionResult item = results[i];
// Result coordinates are floats ranging from 0.00 to 1.00
// Multiply with RTSP resolution to get coordinates in pixels
int xmin = (int)(item.xMin() * im_w);
int xmax = (int)(item.xMax() * im_w);
int ymin = (int)(item.yMin() * im_h);
int ymax = (int)(item.yMax() * im_h);
uint32_t osd_color;
if (String(item.name()) == String("unknown")) {
osd_color = OSD_COLOR_RED;
} else {
osd_color = OSD_COLOR_GREEN;
}
// Draw boundary box
printf("Face %d name %s:\t%d %d %d %d\n\r", i, item.name(), xmin, xmax, ymin, ymax);
OSD.drawRect(CHANNEL, xmin, ymin, xmax, ymax, 3, osd_color);
// Print identification text above boundary box
char text_str[40];
snprintf(text_str, sizeof(text_str), "Face:%s", item.name());
OSD.drawText(CHANNEL, xmin, ymin - OSD.getTextHeight(CHANNEL), text_str, osd_color);
}
}
OSD.update(CHANNEL);
}
0x04 成果及演示视频




演示视频https://www.bilibili.com/video/BV1ikoQYsEp9/
0x05 其他拓展
想拓展增加功能:检测到人脸时录制视频保存至 sd 卡,然后可取消 rstp 推流,不依赖网络
应用场景:放在工位或其他监控位置,直接离线运行 AI 模型推理人脸识别,识别到陌生人则录制视频,保存至 sd 卡,需要时再查看 sd 卡内的视频文件
但是我目前问题是识别不了我的 sd 卡,已经按要求格式化为 FAT32 文件系统了还是不行...


用示例读取 sd 卡容量也不行 ...

感觉是 sd 卡物理损坏了,后面再买个卡试试看
对了... 还想吐槽一下 Rd-04 的引脚,居然不是 2.54 间距的... 还有 IIC_EN 引脚为什么不一起焊上排针...为什么 MCU 还要自己拆掉...