一、项目需求与硬件介绍
要求完成一个基于Sipeed M1s Dock 的网络相机。具体任务包括:完成相机驱动,定时拍摄图片,并将图片通过网络传到电脑或服务器,实现长时间拍摄;然后通过电脑端编程将图片合成为一个视频。
Sipeed M1s Dock 是基于 Sipeed M1s 模组来设计的一款核心板。该模组的主芯片是BL808 RISC-V 480Mhz + NPU BLAI-100,它支持2.4G WiFi。核心板搭载了种类丰富的外设,包括:USB 转 UART 调试器、1.69 寸 240x280 电容触摸屏、200W 像素摄像头等。
二、设计思路
首先,从配置文件读取WiFi接入点和服务器信息。默认的e907核心固件已划出7MB左右的Flash存储空间用作虚拟U盘来存放文件数据。将配置文件存放在这一区域的优势在于,更换WiFi接入点时,无需通过串口输入数据。
接着,根据从服务器(笔记本电脑)向M1s发送数据,触发拍摄并上传。个人能力有限,每次上传需重新建立连接。服务器的上位机程序(基于Python)在硬盘中保存图片,同时添加时间戳水印。
最后,断开M1s的电源,结束拍摄。上位机使用Python的OpenCV将图片合成视频。
三、技术细节
这里不得不说,如果这M1s模组有ESP32那样丰富的例程资源,那么它的价值会提升无数倍。API虽然较为齐全,对像我一样的新手而言,没有充足例程的板只能当装饰品放着观赏啊。
3.1 例程的bug
Sipeed提供了一个网络相机有关的例程,能够将拍摄的图片传送到指定的服务器。经过交流群的讨论,我也发现e907核心的默认固件存在问题,如图所示。需要修正“m1s_e907_xram_wifi.c”文件中,有关IP地址的设定(这一bug在最新的SDK已被修复)。
另外,第一次编译e907固件时,将Ubuntu虚拟机的内存设置为8GB。我发现,如果内存设置为4GB,尽管不会影响c906核心的代码编译,编译e907核心的固件时系统崩溃。当然,第二次编译e907固件时,虚拟机不一定需要8GB的内存,因为编译器可以重复利用之前编译好的相关文件。
3.2 文件读取
仿照其他例程的做法,采用aos读取文件。首先用aos_open获取文件描述符,然后用aos_lseek(fd,0,SEEK_END) 获取该文件的大小。此时,直接进行aos_read是不能读到文件的,因为文件指针受上一步操作的影响,停留在末尾。所以,在读取文件之前,通过aos_lseek(fd,0,SEEK_SET) 复位文件指针。最后,将读取的文件写入缓存并关闭文件。
再经过一些常规的字符串指针操作后,程序正确地从配置文件中读取了SSID、密码、服务器IP地址和端口。
int fd = -1;
int file_len=-1;
int ret=-1;
unsigned char file_buf[4096];
if (0 > (fd = aos_open("/flash/cfg/wifi_cfg.txt", 0))) {
printf("[failed] open configuration file\r\n");
return;
}
if(0>(file_len = aos_lseek(fd, 0, SEEK_END))){
printf("[failed] read file\r\n");
return;
}
aos_lseek(fd, 0, SEEK_SET);
memset(file_buf,0,4096);
if(0>(ret = aos_read(fd, file_buf, file_len))){
printf("[failed] read file data\r\n");
return;
}
printf("[success] file length: %d\r\n",file_len);
//printf("%s\r\n",file_buf);
aos_close(fd);
char* ssid=NULL;
char* ssid_pwd=NULL;
char* server_ip=NULL;
char* server_port=NULL;
int int_port=0;
int i=0;
int j=0;
while(i<file_len && file_buf[i]!=0x0a) i++;
ssid=malloc(i);
memset(ssid,0,i);
memcpy(ssid,file_buf,i-1);
j=i+1;i=0;
while(i<file_len && file_buf[j+i]!=0x0a) i++;
ssid_pwd=malloc(i);
memset(ssid_pwd,0,i);
memcpy(ssid_pwd,file_buf+j,i-1);
j=j+i+1;i=0;
while(i<file_len && file_buf[j+i]!=0x0a) i++;
server_ip=malloc(i);
memset(server_ip,0,i);
memcpy(server_ip,file_buf+j,i-1);
j=j+i+1;i=0;
while(i<file_len && file_buf[j+i]!=0x0a) i++;
server_port=malloc(i);
memset(server_port,0,i);
memcpy(server_port,file_buf+j,i-1);
int_port=atoi(server_port);
printf("ssid=%s\r\n",ssid);
printf("pwd=%s\r\n",ssid_pwd);
printf("ip=%s\r\n",server_ip);
printf("port=%d\r\n",int_port);
3.3 上位机接收图片
网上查找到了基于Python-OpenCV的相关代码。用户根据实际的WiFi情况,通过修改Python代码,设置服务器(电脑)的IP和端口地址。首先建立TCP服务器,然后接收图片文件的长度,并向M1s发送一个字符要求回传,然后M1s将图片完整传回,最后关闭本次TCP连接。的确,这一流程会让帧率大打折扣(5~6 fps)。考虑到项目要求做网络相机,主要功能是图片拍摄,帧率低略些也能满足要求。具体代码请看附件中的Python文件。
接收图片时,我用OpenCV的putText函数,向每张图片的右下角添加当前时刻的水印,模拟“视频监控”的效果。未来,这一操作计划放到M1s进行。
3.4 上位机合成视频
网上查找到了基于Python-OpenCV的相关代码。为了使用方便,上位机程序在命令行运行时,可以添加一些参数,设定运行模式,具体如下:
“--mode”表示运行模式,设置为“record”进入拍摄模式,“make_video”进行视频合成;
“--out_path”表示视频文件的输出路径,而“—cache_path”表示拍摄图片的缓存路径;
“--delay”表示上位机向M1s发出图片回传要求的时间间隔,可粗略调节拍摄帧率;
“--fps”表示视频合成的输出帧率。
parser=argparse.ArgumentParser(description='')
parser.add_argument('--mode',type=str,default='record',help='record or make_video')
parser.add_argument('--out_path',type=str,default='./Video.avi',help='path of the video')
parser.add_argument('--cache_path',type=str,default='./tmp',help='path of the cached pictures')
parser.add_argument('--delay',type=int,default=1,help='control the record frequency')
parser.add_argument('--fps',type=int,default=5,help='video fps')
args=parser.parse_args()
四、实现功能
基于例程,我完成了一个基于Sipeed M1s Dock 的网络相机。该相机具备定时拍摄图片的功能,可以将图片通过WiFi传到电脑,实现长时间拍摄;M1s具体连接的WiFi和服务器的地址端口通过配置文件修改,十分便捷。然后在电脑端的上位机程序,将图片合成为一个视频。
我在室内用M1s拍摄视频的具体效果见演示视频。
五、遇到的其他问题
5.1 例程
本来想做GBA游戏机的,功放和音频ADC模块都做好了,但是没有音频方面的例程,没有看明白相关API怎么使用。等官方完善例程后,我相信能完成它。(不过,新版的例程删去了GBA模拟器这个例子?)
六、未来计划
该项目已基本实现所要求的全部功能。首先,希望优化上位机的代码,提高拍摄帧率,争取达到20fps左右。其次,希望等到例程完善后,尝试M1s的其他功能,制作个GBA模拟器、运行基于BLAI NPU的神经网络算法。最后,希望能将目标检测/人脸检测算法与网络相机结合。
期待M1s的生态逐步完善!