一、项目描述
(一)项目介绍
该项目目标是完成一个基于Sipeed M1s Dock 的网络相机,相机端定时完成拍摄,将拍摄图片通过wifi传到电脑或服务器中,并在电脑或服务器上编写上位机程序,实现接收图片并合成一个视频。
(二)设计思路
- M1S端设计思路
以例程camera_streaming_through_wifi和camera_bypass_lcd为基础,实现通过网络传输摄像头拍摄的图片。设计思路为:在c906芯片上编写初始化相机和WiFi的程序(之后又增加了屏幕显示功能,需初始化屏幕串口),将wifi名称、密码等信息使用IPC功能通过xram传递给e907芯片;e907芯片连接wifi,向指定ip和端口定时发送实时拍摄图片,同时可以接收电脑端程序发送的定时时间,做为下一次图片发送的间隔。
2. 电脑端上位机设计思路
电脑端程序基于python语言开发,使用socket库创建tcp服务端连接,监听发来的信息;使用cv2库解析mjpeg图片、展示图片、合成视频并保存;使用thread库创建多线程,使程序能够同时维持openCV的运行和接收信息,也为之后拓展连接多个设备的开发做准备;使用time库获取时间,作为图片接收时间记录的依据和计算图片间隔时间的依据,用于辅助调试。
(三)软件流程
M1S开发流程图
电脑上位机接收软件开发流程图
二、硬件介绍
Sipeed M1s Dock模块包含BL808主芯片、触摸屏、摄像头、wifi蓝牙芯片等硬件设施。BL808主芯片包含M0,D0,LP三个处理器,M0是32位处理器,主要负责处理网络数据流和外设访问功能;D0是64位处理器,主要负责多媒体数据的计算和处理;LP负责低功耗模式下的相关功能。
本项目使用M0处理器(e907)进行WiFi连接和图片推流;使用D0处理器(c906)进行图片实时拍摄和屏幕显示。
三、功能展示
(一)实物
(二)电脑端
(三)最终输出视频
四、代码片段说明
(一)相机读取、屏幕输出
//读取相机缓存内容
while(1){
while (0 != bl_cam_mjpeg_get(&pic, &len)) {
vTaskDelay(1);
}
while (0 != bl_cam_mipi_rgb565_frame_get((uint8_t **)&picture, &length)) {
vTaskDelay(1);
}
printf("[%8u]get mipi rgb565 frame 0x%08x size %ubytes cost %fms ", loop_cnt, (uint32_t)picture, length, (float)(CPU_Get_MTimer_US() - curr_cnt) / 1000);
BilinearInterpolation_RGB565(picture, 400, 300, draw_buf, 280, 240); //改变尺寸
curr_cnt = CPU_Get_MTimer_US();
//写入屏幕
for (int i = 0; i < 280 * 240; i++) {
draw_buf[i] = __builtin_bswap16(draw_buf[i]);
}
st7789v_spi_draw_picture_nonblocking(0, 0, 279, 239, draw_buf);
printf("and lcd_flush cost %fms\r\n", (float)(CPU_Get_MTimer_US() - curr_cnt) / 1000);
//更新相机缓存
bl_cam_mipi_rgb565_frame_pop();
bl_cam_mjpeg_pop();
loop_cnt++;
vTaskDelay(100);
}
循环运行,分别读取mjpeg和rgb565两种图片,mjpeg格式用于输出800*600大小图片推流到电脑端,rgb565用于屏幕显示,之后使用对应pop函数清除缓存。
(二)Wifi推流(components\sipeed\e907\m1s_e907_xram\src\m1s_e907_xram_wifi.c)
while (1) {
if((CPU_Get_MTimer_MS()>time_delay)&&flush){
ret = bl_cam_mjpeg_get(&pic, &len);
csi_dcache_invalid_range((void *)pic, len);
if (ret == 0) {
if (((uint32_t)(uintptr_t)pic + len) > (mjpeg_start_addr + mjpeg_buffer_size)) {
/* if mjpeg store edge loop to start*/
first_len = mjpeg_start_addr + mjpeg_buffer_size - (uint32_t)(uintptr_t)pic;
second_len = len - first_len;
csi_dcache_invalid_range((void *)pic, first_len);
memcpy(private.stream_buff, pic, first_len);
csi_dcache_invalid_range((void *)mjpeg_start_addr, second_len);
memcpy(private.stream_buff + first_len, (void *)mjpeg_start_addr, second_len);
usb_ptr = private.stream_buff;
} else {
/*mjpeg data not cut*/
usb_ptr = pic;
csi_dcache_invalid_range((void *)usb_ptr, len);
}
uint32_t recv;
_retry2:
printf("send jpg len(%d):%ld\r\n", sizeof(len), len);
if (write(sock, &len, sizeof(len)) < 0) break;
if (read(sock, &recv, sizeof(recv)) < 0) {
vTaskDelay(50);
goto _retry2;
}
delay=recv;
#define PACK_LEN (1000)
int remain_len = len;
if (write(sock, usb_ptr, remain_len) < 0) break;
if (read(sock, &recv, sizeof(recv)) < 0) break;
if (recv==0) continue;
if(delay<1000){
if(recv>200)vTaskDelay(recv-300);
flush=true;
}else{
flush=false;
if((uint32_t)err>delay) err=0;
time_delay=time+(uint64_t)((int)delay-err);
//printf("%lld\n",time);
//printf("%lld\n",time_delay);
}
if(flush){
bl_cam_mjpeg_pop();
}
}
}else{
uint64_t now=CPU_Get_MTimer_MS();
if(time_delay>(now+2000)) {
vTaskDelay(2000);
uint32_t send=1;
if (write(sock, &send, sizeof(send)) < 0) break;
}else{
now=CPU_Get_MTimer_MS();
if(time_delay>now){
vTaskDelay(time_delay-now);
continue;
}
int temp=(int)((now-time)-delay);
if(temp<500) err+=temp;
printf("delay:%lld;+/-:%i\n\n",now-time,err);
time=now;
flush=true;
}
}
}
图片推流程序代码,循环处理。当达到发送间隔时间,且未发送成功时,持续读取相机图片,发送图片,发送成功后收到电脑端回应发送间隔,根据发送间隔计算下一次发送时间,未到时间持续循环等待,不发送。
五、主要难题与解决方案
(一)无法连接到WiFi
库文件中components\sipeed\e907\m1s_e907_xram\src\m1s_e907_xram_wifi.c文件存在问题,wifi的ip和端口被写死,无法与C906主函数中填写的wifi配置一致,更改后即可。
(二)图片推流问题
无法确定推流图片电脑端是否完整接收,导致有时解码不出来,程序出现问题,同时BL808部分不会重传。
解决办法为接收到信息后验证信息,回复是否接收完整。BL808接收到回复信息后完成下一步操作,接收到的代码为不成功时进行重传。后又增加了更改间隔时间功能,若接收成功,电脑端回复间隔时间,BL808根据新的间隔时间发送图片。
(三)无法同时推流和屏幕显示
屏幕显示rgb565格式,推流mjpeg格式,且图片尺寸不同。mjpeg对图片进行压缩,适合网络传输,rgb565有合适的尺寸大小转换函数且可以直接写入屏幕的缓存,适合各自用途,最好的解决办法为分别生成两种图片。
经过实验发现,直接初始化两种格式后,只保留最后一次的设置,之前的初始化被覆盖,所以显示有问题。解决方法为,在components\platform\hosal\bl808_hal\bl_cam.c文件中,修改其中初始化函数,在当中修改DSP2_MISC_Scaler_Input_Select (DSP2_MISC_SCALER_1_ID, DSP2_MISC_SCALER_DSP2_INPUT);和DSP2_Scaler_Set_Input (DSP2_MISC_SCALER_B, DSP2_MISC_SCALER_DSP2_INPUT);
/*change DSP2_MISC_SCALER_2_ID to DSP2_MISC_SCALER_1_ID by myself --YuHao*/
DSP2_MISC_Scaler_Input_Select(DSP2_MISC_SCALER_1_ID, DSP2_MISC_SCALER_DSP2_INPUT);
DSP2_MISC_Scaler_Init(DSP2_MISC_SCALER_1_ID, &scaler_cfg);
DSP2_MISC_Scaler_Enable(DSP2_MISC_SCALER_1_ID);
/*change DSP2_MISC_SCALER_C to DSP2_MISC_SCALER_B by myself --YuHao*/
DSP2_Scaler_Set_Input(DSP2_MISC_SCALER_B, DSP2_MISC_SCALER_DSP2_INPUT);
DSP2_YUV2RGB_Init(DSP2_YUV2RGB_PARAM_8BIT_BT601);
DSP2_YUV2RGB_Set_Input(DSP2_YUV2RGB_A, DSP2_YUV2RGB_INPUT_SCALER_B);
六、未来计划
可以在该模块上加入AI模型,拍摄图片的同时对图片进行一些识别。例如结合项目3、4的人脸识别模块,对识别到的人脸进行拍摄上传,同时发送识别结果。也可以通过连接舵机等设备,达到人脸跟踪和人形跟踪的效果。同时调用屏幕的触摸功能,设计人机交互,可以通过此功能设置wifi、选择工作模式、增删人脸识别结果等。通过这样的一个模块,未来可以实现很多功能。
七、附件(源码)
链接:https://pan.baidu.com/s/1sr7bc4wBUzB1GqaAmvXiFQ?pwd=5po8
提取码:5po8