基于 Sipeed M1s Dock 的网络相机
本项目使用 M1s Dock 开发板和上位机 Python GUI程序实现了一个网络相机,包含实时网络流图像传输显示、视频录制功能。
标签
Python
2023寒假在家练
Sipeed M1s Dock
上位机
2x3j
更新2023-03-28
中国海洋大学
526

内容介绍

功能介绍

本项目使用 M1s Dock 开发板和上位机 Python GUI程序实现了一个网络相机,包含实时网络流图像传输显示、视频录制功能。M1s Dock 开发板是基于博流智能科技的 BL808 芯片设计的一款 AIOT 模组,主控芯片包含三个 RISC-V 核心,具有 WiFi/BT/BLE/Zigbee 等无线互联单元,包含多个 CPU 以及音频编码译码器、视频编码译码器和 AI 硬件加速器(BLAI-100),适用于各种高性能和低功耗应用领域。

本项目利用 M1s Dock 开发板上的 MIPI 摄像头采集图像,并通过 socket 协议将图像数据转发到其他网络设备。上位机使用 python GUI 程序接收图像数据,并进行实时显示。上位机也可以通过 GUI 界面控制视频录制功能,并通过 opencv 将实时图像数据写入到视频文件中。

硬件介绍

FrUPHgxmjTe8_PABHMZ4TaC_ryUG

  • 主芯片 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 接口

M1s dock 开发板有两个核心,分别是 BL808 芯片的 C906 核心和 E907 核心。

  • C906 核心是一个 RV64GCV 架构的 RISC-V 处理器,运行在 480MHz 的频率,支持 AI NN 通用硬件加速器 BLAI-100,用于视频/音频检测/识别等任务。

  • E907 核心是一个 RV32GCP 架构的 RISC-V 处理器,运行在 320MHz 的频率,支持 Wi-Fi 802.11 b/g/n 和 Bluetooth 5.x Dual-mode (BT+BLE) 等无线功能。

一般情况下对于 C906 进行编程。

 

整体架构

项目整体分成两个部分,嵌入式端和上位机:

  • 嵌入式端:通过 WIFI 接入网络,使用 socket 协议将捕获的图片传输到指定的地址

  • 上位机:建立 socket 服务,接收来自 MCU 的图片,并通过 GUI 进行显示,支持通过 opencv 将图片写入到视频文件

FvXv6HZRY1zKRHJ9bGEeFlToJm79

实现细节

在嵌入式端,Sipeed 官方已经提供了图像传输相关的函数。刚拿到开发板时,SDK 中的 socket 推流函数不能够正确传参,但官方在后续的更新中解决了这个问题,新版 SDK 可以正常工作。

在实现代码时,我遇到了一个困难,开发板出厂自带的 firmware 不能运行 camera_stream_through_wifi 例程,需要更新 firmware 才能正常工作。这是因为开发板上的 BL808 芯片有两个 RISC-V 核心,分别是 E907 和 C906,分别运行不同的 firmware。出厂自带的 firmware 可能不支持某些功能或者有 bug,所以需要更新到最新的 firmware 才能保证正常运行。

更新 firmware 的方法有两种,一种是通过 U 盘烧录,一种是通过串口烧录。U 盘烧录只能更新 C906 核心的 firmware,串口烧录可以同时更新 E907 和 C906 核心的 firmware。我选择了串口烧录的方法,具体步骤如下:

  1. 使用 TypeC 数据线将电脑与开发板的 UART 口连接起来,在电脑上会显示两个串口设备。

  2. 下载博流官方烧录工具 BLDevCube,并在软件中选择 BL808 芯片。

  3. 选择分区表文件 partition_cfg_16M_m1sdock.toml,以及 boot2、firmware 和 d0fw 三个固件文件。boot2 是固定的,位于 BLDevCube\chips\bl808\builtin_imgs\boot2_isp_bl808_xxxx_xxx 目录下;firmware 是 E907 核心运行的固件;d0fw 是 C906 核心运行的固件。

  4. 在窗口右侧点击 Refresh 来刷新串口,并选择串口号较大的那个串口。

  5. 按住开发板上的 BOOT 键和 RST 键,然后先松开 RST 键再松开 BOOT 键来使开发板进入串口烧录模式。

  6. 点击 Create & Download 后会开始烧录过程。如果握手失败或者超时,请重新操作第 5 步和第 6 步。

  7. 烧录完成后,开发板会自动重启,并运行最新的 firmware。

FmCR_3nPUDKKm-LdJ78fva_ud6k9

程序整体流程如图所示,可以分为 GUI 和图像接受线程两部分,线程被启动之后开启服务器循环接收来图片,并存储到成员变量当中,GUI 需要刷新时通过成员变量获取具体的图像数据。

# 封装 socket 接收图片类
# 类内会为接收图片创建一个线程,线程内会不断接收图片数据
# 接收到图片数据后,会将图片数据保存到类的成员变量中
# 类的成员变量可以通过 get_image_data() 方法获取
# 包含录制视频功能
class SocketImageReceiver:
    def __init__(self, ip, port):
        self._ip = ip
        self._port = port
        self._image_data = None
        self._thread = None
        self._is_running = False
        self._is_recording = False
        self._video_writer = None
        
    
    def start(self):
        self._thread = threading.Thread(target=self._run)
        self._thread.setDaemon(True)
        self._thread.start()

    def stop(self):
        # 如果正在录制视频,则停止录制视频
        if self._is_recording:
            self.stop_record()
        
        self._is_running = False
        self._thread.join()
    
    def start_record(self):
        # 创建视频写入对象
        # 参数1:文件名
        # 参数2:编码器,这里使用 DIVX 编码器
        # 参数3:帧率
        # 参数4:图像大小

        # 图像大小从类的成员变量中获取
        # 文件以时间戳命名,避免文件名重复
        fname = time.strftime("%Y%m%d%H%M%S", time.localtime()) + ".avi"
        self._video_writer = cv.VideoWriter(fname, cv.VideoWriter_fourcc(*'DIVX'), 15, self._image_data.shape[1::-1])
        self._is_recording = True
        print("开始录制视频")
        
    
    def stop_record(self):
        self._is_recording = False
        self._video_writer.release()
        print("停止录制视频")

    def _run(self):
        # 创建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((self._ip, self._port))
        # 设置监听,把主动套接字变为被动套接字,被动套接字只负责接收客户端的连接请求,accept方法会等待客户端的连接请求,如果有客户端连接过来,就会返回一个新的套接字专门为这个客户端服务。
        tcp_server.listen(128)
        # 等待客户端的连接请求
        print("等待客户端的连接请求...")
        self._is_running = True
        
        tcp_client, client_addr = tcp_server.accept()
        print("客户端已连接,客户端的ip地址为:", client_addr)
        while self._is_running:
            # 接收客户端发送的数据, 这次接收数据的最大字节数是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)

            self._image_data = img
            
            # 如果当前正在录制 则将图像数据写入视频文件
            if self._is_recording:
                self._video_writer.write(img)


        tcp_client.close()
        tcp_server.close()
    
    def get_image_data(self):
        return self._image_data
 



总结

本项目展示了 M1s 开发板的强大功能和灵活性,可以实现多种网络相机的应用场景,如视频监控、智能门铃、远程会议等。同时也体现了 RISC-V 架构的优势,可以支持多种操作系统和开发方式。

附件下载

sipeed.zip
程序源码

团队介绍

个人团队

评论

0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号