用Sipeed M1s Dock实现网络相机
用Sipeed M1s Dock实现网络相机,完成相机驱动,定时拍摄图片,并将图片通过网络传到电脑或服务器,实现长时间拍摄,通过电脑端编程将图片合成为一个视频。
标签
2023寒假在家练
网络相机
M1s Dock
sipeed
doudou
更新2023-03-29
557

用Sipeed M1s Dock实现网络相机

1 项目介绍

这是电子森林2023寒假一起练平台(3)- 基于Sipeed M1s Dock综合应用的项目。我完成的是项目5 -  网络相机。项目实现了用Sipeed M1s Dock实现网络相机,完成相机驱动,定时拍摄图片,并将图片通过网络传到电脑或服务器,实现长时间拍摄,通过电脑端编程将图片合成为一个视频。

2 设计思路

首先完成M1s Dock摄像头的驱动,接着实现M1s Dock定时拍照功能,然后M1s Dock通过wifi连接到路由器,再通过TCP将照片发送到局域网内服务器,最后电脑端Python程序完成视频的合成。

3 框图和软件流程图

FnbrCCNGv6C-KBYkJn0I74Otoi5M


4 简单的硬件介绍

FqaedFo8ZSlgd_hYfM6cfDl_mYCS

 

1、硬件参数:

  • 主芯片 BL808 RISC-V 480Mhz + NPU BLAI-100
  • 板载 USB 转 UART 调试器(可实现一键点击烧录,无需按实体按键)
  • 1.69 寸 240x280 电容触摸屏
  • 200W 像素摄像头
  • 支持 2.4G WIFI / BT / BLE
  • 板载 1 个模拟麦克风、1 个 LED、1 个 TF 卡座
  • 引出一路 USB-OTG 到 USB Type-C 接口

2、引脚图

FrUPHgxmjTe8_PABHMZ4TaC_ryUG

5 实现的功能及图片展示

5.1 安装WSL2

WSL是适用于 Linux 的 Windows 子系统,可以安装 Linux 发行版(例如 Ubuntu),并直接在 Windows 上使用 Linux 应用程序、实用程序和 Bash 命令行工具。

具体可以参考微软官方的文档。

https://learn.microsoft.com/zh-cn/windows/wsl/install

5.2 安装Ubuntu22.04.2LTS

打开Microsoft Store,安装Ubuntu22.04.2LTS。

FlGLAQtSQfxB2C7BQcSeUQ2mXVA7

5.3 配置SDK编译环境

1、安装编译所需要的相关软件

sudo apt-get install git make tree

Fk2qT6PLNvwF1XoECGXNYA14WjXc

2、获取例程仓库

git clone https://gitee.com/Sipeed/M1s_BL808_example.git

Fl7VUytXlgd2HXlVRtW7NLr6vbL4

3、获得 SDK 仓库

git clone https://gitee.com/sipeed/M1s_BL808_SDK.git

FuCbqRnxQMgcBLXL4m5v7457jL-6

4、在 SDK 仓库文件夹下,获取编译工具链

mkdir -p M1s_BL808_SDK/toolchaincd M1s_BL808_SDK/toolchain
git clone https://gitee.com/wonderfullook/m1s_toolchain.git
mv m1s_toolchain Linux_x86_64
cd ../../

Ft9Dt6EFHaYd1nlut447l_fQXNGn

5、确定 M1s_BL808_SDK 文件夹所在的路径

cd M1s_BL808_SDK
pwd

FhZze_-YrGaku-smSrMLCVW4pyYU

6、配置编译工具链路径

export BL_SDK_PATH=/home/user/BL808/M1s_BL808_SDK

FmtJ4YXftpidlptG_OXZDd5LMnun

5.4 修改并编译DEMO

1、修改camera_streaming_through_wifi官方DEMO

打开例程路径 M1s_BL808_example/c906_app/camera_streaming_through_wifi 中的 main.c 文件。

camera_stream_original_source_code

修改main.c文件中m1s_xram_wifi_connect()函数后面的wifi的SSID和密码,修改m1s_xram_wifi_upload_stream()函数后的服务器地址为本机IP地址。

#include <stdbool.h>
#include <stdio.h>

/* FreeRTOS */
#include <FreeRTOS.h>
#include <task.h>

/* bl808 c906 std driver */
#include <bl808_glb.h>
#include <bl_cam.h>

#include <m1s_c906_xram_wifi.h>

void main()
{
    vTaskDelay(1);
    bl_cam_mipi_mjpeg_init();

    m1s_xram_wifi_init();
    m1s_xram_wifi_connect("doudou", "88888888");
    m1s_xram_wifi_upload_stream("192.168.2.102", 8888);
}

2、编译SDK

cd M1s_BL808_example/c906_app
./build.sh camera_streaming_through_wifi

FhANLMYVo1NWP4uf3XgFNfnDJIpo

 

然后编译出来的固件就会在 M1s_BL808_example/e907_app/build_out 目录下,名称为 firmware.bin.

Flhhv8AfkB6xV-tXIUtTnmMbRuyX

5.5 烧录固件

1、下载官方图形化烧录工具

前往 https://dev.bouffalolab.com/download 下载名称为 Bouffalo Lab Dev Cube 的文件。

或者点击https://dev.bouffalolab.com/media/upload/download/BouffaloLabDevCube-v1.8.3.zip链接直接下载。

bouffalo_cube

2、烧录

打开Bouffalo Lab Dev Cube,选择BL808,如下图所示:

chip_selection

点击Browse,选择分区表文件partition_cfg_16M_m1sdock.toml,如下图所示:

分区表文件从https://dl.sipeed.com/fileList/MAIX/M1s/M1s_Dock/7_Firmware/partition/partition_cfg_16M_m1sdock.toml下载,附件里也有。

choose_partition

依次选择boot2(官方提供默认boot2文件boot2_isp_debug.bin)、d0fw(刚才编译的SDK文件,)、firmware(官方提供默认firmware文件)固件文件

 

boot2:位于 BLDevCube\chips\bl808\builtin_imgs\boot2_isp_bl808_v6.5.4下面

Fk-5FHQrM21v6934uB3Dllx2JJZ7

d0fw:就是刚才编译的SDK文件,位于M1s_BL808_example/e907_app/build_out目录下,名称为 firmware.bin

firmware:使用官方提供的默认firmware,可以从下面链接下载

https://dl.sipeed.com/fileList/MAIX/M1s/M1s_Dock/7_Firmware/factory/firmware_20230227.bin

firmware_choose

用TypeC的数据线连接UART口到电脑,点击Rerresh按钮进行刷新。在Interface中选择Uart,从Port/SN显示的连续的2个端口号中,选择数字比较大的端口号。

burn_steps

同时按住板子上的 BOOT 键和 RST 键, 然后先松开 RST 键,再松开 BOOT 键,使板子进入串口烧录模式。

boot_rst

点击下载 Create & Download按钮进行烧录。

finish_burning

如上图所示,进度条显示100%,下方log显示“All Success”,表示烧录成功。

 

2、编写Python程序

import socket
import os.path
import numpy as np
import cv2 as cv

img_path = r"D:\\CV_image\\"
viedo_path = r"D:\\CV_viedo\\"
count = 1
name_count = 1
viedo_count = 1
file_dir = 'd:/CV_image/'
def viedoSave():
    list = []
    for root ,dirs, files in os.walk(file_dir):
        for file in files:
            list.append(file)      # 获取目录下文件名列表

    # VideoWriter是cv2库提供的视频保存方法,将合成的视频保存到该路径中
    # 'MJPG'意思是支持jpg格式图片
    # fps = 5代表视频的帧频为5,如果图片不多,帧频最好设置的小一点
    # (800,600)是生成的视频像素800*600,一般要与所使用的图片像素大小一致,否则生成的视频无法播放
    # 
    video = cv.VideoWriter(viedo_path + str(viedo_count) + '.avi',cv.VideoWriter_fourcc(*'MJPG'),1,(800,600))

    for i in range(1,len(list)):
    #for i in range(1,60):
        #读取图片
        img = cv.imread(img_path + list[i-1])  
      #  img = cv.imread(img_path + str((viedo_count - 1) * 60 + i))     
        # resize方法是cv2库提供的更改像素大小的方法
        # 将图片转换为800*600像素大小
        # img = cv.resize(img,(800,600))
        # 写入视频
        video.write(img)

if __name__ == '__main__':
    # 创建tcp服务端套接字
    # 参数同客户端配置一致,这里不再重复
    tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 设置端口号复用,让程序退出端口号立即释放,否则的话在30秒-2分钟之内这个端口是不会被释放的,这是TCP的为了保证传输可靠性的机制。
    tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

    # 给客户端绑定端口号,客户端需要知道服务器的端口号才能进行建立连接。IP地址不用设置,默认就为本机的IP地址。
    tcp_server.bind(("", 8888))

    # 设置监听
    # 128:最大等待建立连接的个数, 提示: 目前是单任务的服务端,同一时刻只能服务与一个客户端,后续使用多任务能够让服务端同时服务与多个客户端
    # 不需要让客户端进行等待建立连接
    # listen后的这个套接字只负责接收客户端连接请求,不能收发消息,收发消息使用返回的这个新套接字tcp_client来完成
    tcp_server.listen(128)

    # 等待客户端建立连接的请求, 只有客户端和服务端建立连接成功代码才会解阻塞,代码才能继续往下执行
    # 1. 专门和客户端通信的套接字: tcp_client
    # 2. 客户端的ip地址和端口号: tcp_client_address
    tcp_client, tcp_client_address = tcp_server.accept()

    # 代码执行到此说明连接建立成功
    print("客户端的ip地址和端口号:", tcp_client_address)

    timeF = 15  # 视频帧计数间隔频率
    while True:
        # 接收客户端发送的数据, 这次接收数据的最大字节数是4
        recv_data = tcp_client.recv(4)
        mjpeg_len = int.from_bytes(recv_data, 'little')
        print("recv len: ", mjpeg_len)
        tcp_client.send(recv_data)
        recv_data_mjpeg = b''
        remained_bytes = mjpeg_len
        while remained_bytes > 0:
            recv_data_mjpeg += tcp_client.recv(remained_bytes)
            remained_bytes = mjpeg_len - len(recv_data_mjpeg)
        print("recv stream success")
        if recv_data_mjpeg[:2] != b'\xff\xd8' \
                or recv_data_mjpeg[-2:] != b'\xff\xd9':
            continue
        mjpeg_data = np.frombuffer(recv_data_mjpeg, 'uint8')
        img = cv.imdecode(mjpeg_data, cv.IMREAD_COLOR)
        cv.imshow('stream', img)
        if (count % timeF == 0):  # 每隔timeF帧进行存储操作
            cv.imwrite(img_path + str(name_count) + '.jpg', img)
            name_count=name_count+1
        count = count + 1
        if (count % 60 == 0):
            viedoSave()
            viedo_count = viedo_count + 1
            
        if cv.waitKey(1) == 'q':
            exit(0)



    
    # 关闭服务与客户端的套接字, 终止和客户端通信的服务
    tcp_client.close()

    # 关闭服务端的套接字, 终止和客户端提供建立连接请求的服务 但是正常来说服务器的套接字是不需要关闭的,因为服务器需要一直运行。
    # tcp_server.close()
6 主要代码片段及说明
 
6.1 导入库
import socket
import numpy as np
import cv2 as cv

6.2 定义变量

img_path = r"D:\\CV_image\\"
viedo_path = r"D:\\CV_viedo\\"
count = 1
name_count = 1
viedo_count = 1
file_dir = 'd:/CV_image/'

6.3 创建tcp服务端套接字

tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

6.4 设置端口号复用,让程序退出端口号立即释放,否则的话在30秒-2分钟之内这个端口是不会被释放的,这是TCP的为了保证传输可靠性的机制。

tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

6.5 给客户端绑定端口号,客户端需要知道服务器的端口号才能进行建立连接。IP地址不用设置,默认就为本机的IP地址。

tcp_server.bind(("", 8888))

6.6 设置监听,128是最大等待建立连接的个数

tcp_server.listen(128)

6.7 等待客户端建立连接的请求

tcp_client, tcp_client_address = tcp_server.accept()

6.8 打印客户端IP地址和端口号,用于判断建立连接是否成功

print("客户端的ip地址和端口号:", tcp_client_address)

6.9 接收客户端发送的数据

 # 接收客户端发送的数据, 这次接收数据的最大字节数是4
        recv_data = tcp_client.recv(4)
        mjpeg_len = int.from_bytes(recv_data, 'little')
        print("recv len: ", mjpeg_len)
        tcp_client.send(recv_data)
        recv_data_mjpeg = b''
        remained_bytes = mjpeg_len
        while remained_bytes > 0:
            recv_data_mjpeg += tcp_client.recv(remained_bytes)
            remained_bytes = mjpeg_len - len(recv_data_mjpeg)
        print("recv stream success")
        if recv_data_mjpeg[:2] != b'\xff\xd8' \
                or recv_data_mjpeg[-2:] != b'\xff\xd9':
            continue
        mjpeg_data = np.frombuffer(recv_data_mjpeg, 'uint8')
        img = cv.imdecode(mjpeg_data, cv.IMREAD_COLOR)
        cv.imshow('stream', img)

6.10 保存照片

        if (count % timeF == 0):  # 每隔timeF帧进行存储操作
            cv.imwrite(img_path + str(name_count) + '.jpg', img)
            name_count=name_count+1
        count = count + 1

6.11 合成视频

        if (count % 60 == 0):
            viedoSave()
            viedo_count = viedo_count + 1


def viedoSave():
    list = []
    for root ,dirs, files in os.walk(file_dir):
        for file in files:
            list.append(file)      # 获取目录下文件名列表

    # VideoWriter是cv2库提供的视频保存方法,将合成的视频保存到该路径中
    # 'MJPG'意思是支持jpg格式图片
    # fps = 5代表视频的帧频为5,如果图片不多,帧频最好设置的小一点
    # (800,600)是生成的视频像素800*600,一般要与所使用的图片像素大小一致,否则生成的视频无法播放
    # 
    video = cv.VideoWriter(viedo_path + str(viedo_count) + '.avi',cv.VideoWriter_fourcc(*'MJPG'),1,(800,600))

    for i in range(1,len(list)):
    #for i in range(1,60):
        #读取图片
        img = cv.imread(img_path + list[i-1])  
      #  img = cv.imread(img_path + str((viedo_count - 1) * 60 + i))     
        # resize方法是cv2库提供的更改像素大小的方法
        # 将图片转换为800*600像素大小
        # img = cv.resize(img,(800,600))
        # 写入视频
        video.write(img)

 

7 完整代码

import socket
import os.path
import numpy as np
import cv2 as cv

img_path = r"D:\\CV_image\\"
viedo_path = r"D:\\CV_viedo\\"
count = 1
name_count = 1
viedo_count = 1
file_dir = 'd:/CV_image/'
def viedoSave():
    list = []
    for root ,dirs, files in os.walk(file_dir):
        for file in files:
            list.append(file)      # 获取目录下文件名列表

    # VideoWriter是cv2库提供的视频保存方法,将合成的视频保存到该路径中
    # 'MJPG'意思是支持jpg格式图片
    # fps = 5代表视频的帧频为5,如果图片不多,帧频最好设置的小一点
    # (800,600)是生成的视频像素800*600,一般要与所使用的图片像素大小一致,否则生成的视频无法播放
    # 
    video = cv.VideoWriter(viedo_path + str(viedo_count) + '.avi',cv.VideoWriter_fourcc(*'MJPG'),1,(800,600))

    for i in range(1,len(list)):
    #for i in range(1,60):
        #读取图片
        img = cv.imread(img_path + list[i-1])  
      #  img = cv.imread(img_path + str((viedo_count - 1) * 60 + i))     
        # resize方法是cv2库提供的更改像素大小的方法
        # 将图片转换为800*600像素大小
        # img = cv.resize(img,(800,600))
        # 写入视频
        video.write(img)

if __name__ == '__main__':
    # 创建tcp服务端套接字
    # 参数同客户端配置一致,这里不再重复
    tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 设置端口号复用,让程序退出端口号立即释放,否则的话在30秒-2分钟之内这个端口是不会被释放的,这是TCP的为了保证传输可靠性的机制。
    tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

    # 给客户端绑定端口号,客户端需要知道服务器的端口号才能进行建立连接。IP地址不用设置,默认就为本机的IP地址。
    tcp_server.bind(("", 8888))

    # 设置监听
    # 128:最大等待建立连接的个数, 提示: 目前是单任务的服务端,同一时刻只能服务与一个客户端,后续使用多任务能够让服务端同时服务与多个客户端
    # 不需要让客户端进行等待建立连接
    # listen后的这个套接字只负责接收客户端连接请求,不能收发消息,收发消息使用返回的这个新套接字tcp_client来完成
    tcp_server.listen(128)

    # 等待客户端建立连接的请求, 只有客户端和服务端建立连接成功代码才会解阻塞,代码才能继续往下执行
    # 1. 专门和客户端通信的套接字: tcp_client
    # 2. 客户端的ip地址和端口号: tcp_client_address
    tcp_client, tcp_client_address = tcp_server.accept()

    # 代码执行到此说明连接建立成功
    print("客户端的ip地址和端口号:", tcp_client_address)

    timeF = 15  # 视频帧计数间隔频率
    while True:
        # 接收客户端发送的数据, 这次接收数据的最大字节数是4
        recv_data = tcp_client.recv(4)
        mjpeg_len = int.from_bytes(recv_data, 'little')
        print("recv len: ", mjpeg_len)
        tcp_client.send(recv_data)
        recv_data_mjpeg = b''
        remained_bytes = mjpeg_len
        while remained_bytes > 0:
            recv_data_mjpeg += tcp_client.recv(remained_bytes)
            remained_bytes = mjpeg_len - len(recv_data_mjpeg)
        print("recv stream success")
        if recv_data_mjpeg[:2] != b'\xff\xd8' \
                or recv_data_mjpeg[-2:] != b'\xff\xd9':
            continue
        mjpeg_data = np.frombuffer(recv_data_mjpeg, 'uint8')
        img = cv.imdecode(mjpeg_data, cv.IMREAD_COLOR)
        cv.imshow('stream', img)
        if (count % timeF == 0):  # 每隔timeF帧进行存储操作
            cv.imwrite(img_path + str(name_count) + '.jpg', img)
            name_count=name_count+1
        count = count + 1
        if (count % 60 == 0):
            viedoSave()
            viedo_count = viedo_count + 1
            
        if cv.waitKey(1) == 'q':
            exit(0)



    
    # 关闭服务与客户端的套接字, 终止和客户端通信的服务
    tcp_client.close()

    # 关闭服务端的套接字, 终止和客户端提供建立连接请求的服务 但是正常来说服务器的套接字是不需要关闭的,因为服务器需要一直运行。
    # tcp_server.close()

8 实现的功能及图片展示

8.1 拍照功能

FmDK9jjEHz27VRgaIjf5aOD7e9pw

FgH-LoxUzlbik4xiVV7Ayr-xEciB

8.2 视频合成功能

FtZm056fjThoBE6NoAJjCbigun06FvFeV7y7d5PbiEpfPicXRVSsuM2I

9 遇到的主要难题及解决方法

9.1 难题1:WSL2的安装始终不成功。

原因1:windows系统版本过低,,对于 x64 系统:版本 1903 或更高版本,内部版本为 18362 或更高版本。

解决方法:可以尝试通过Windows Update 助手更新系统版本,或者重新安装新版本的windows10或者11。笔者尝试更新版本不成功,最终选择了重新安装win11。

原因2:未安装虚拟化功能

以管理员身份打开 PowerShell 并运行:

dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

9.2 SDK编译环境的配置

原因1:权限问题

Linux下任何操作都提示没有权限,比如git clone

解决方法:删除SDK和example所有目录和文件,重新git下载。

9.3 编译错误

原因:未安装make

解决方法:安装make

sudo apt-get install make

9.4 Python端程序的编写

解决方法:在官方程序的基础上,查询opencv函数,增加保存图片和合成视频的功能。

10 未来的计划或建议

Sipeed的采用BL808主控的M1S Dock相比较采用K210主控的M1具有更大的RAM,更高的核心频率,同时支持蓝牙和Zigbee。目前BL808的用户比较少,教程与案例比较少,

缺乏生态。未来计划主要依托博流和矽速官方github、wiki相关文档、例程进行学习,遇到问题可以向电子森林活动群、矽速官方QQ群寻求帮助。主要学习lvgl、模型训练、人脸识别等。

附件下载
程序及固件.rar
团队介绍
个人
团队成员
doudou
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号