寒假在家一起练(3)- 基于Sipeed M1s Dock的网络相机
完成一个基于Sipeed M1s Dock 的网络相机,完成相机驱动,定时拍摄图片,并将图片通过网络传到电脑或服务器,实现长时间拍摄,通过电脑端编程将图片合成为一个视频。
标签
测试
显示
2023寒假在家练
网络相机
梦南河
更新2023-03-28
河南工业大学
468

项目需求

目标:

      完成一个基于Sipeed M1s Dock 的网络相机

具体要求:

      1.完成相机驱动,定时拍摄图片,并将图片通过网络传到电脑或服务器,实现长时间拍摄

      2.通过电脑端编程将图片合成为一个视频

一.硬件介绍

   主芯片 BL808 RISC-V 480Mhz + NPU BLAI-100

   板载 USB 转 UART 调试器(可实现一键点击烧录,无需按实体按键)

   板载显示屏座子(可选配 1.69 寸 240x280 电容触摸屏)

   板载 MIPI 摄像头座子(可选配 200W 像素摄像头)

   支持 2.4G WIFI / BT / BLE

   板载 1 个模拟麦克风、1 个 LED、1 个 TF 卡座

   引出一路 USB-OTG 到 USB Type-C 接口

   wNZTWzUHIMyPQAAAABJRU5ErkJggg==

M1s Dock详细参数

二.设计思路

      下位机:基于Sipeed M1s Dock的板载WIFI和板载的摄像头接口,通过编程实现连接相应的WiFi将摄像头拍摄的文件上传到固定ip的主机。通过tcp协议Sipeed M1s Dock作为客户端,将摄像头的实时拍摄的jpg文件和文件大小发送到主机服务器。

      上位机:基于python的socket编写服务器,服务器和客户端基于tcp建立连接后,先会接收到客户端发来的文件大小,解析文件大小数据后,根据文件大小判断一帧jpg图片的结束,这样很好的解决了粘包问题。将接收到的jpg文件的数据写进相应文件以jpg格式并且按照拍摄顺序命名保存到固定的目录下。循环接收和写入。

      接收完成之后基于python的opencv,将一系列的jpg文件合成为一个视频。首先设置了一个图像目录,通过 os.listdir 函数获取该目录下所有以 .jpg 结尾的文件,并将其按名称排序。接着设置输出视频文件名和帧率,然后读取第一张图片获取其大小作为视频的分辨率。接下来,使用 cv2.VideoWriter 函数初始化视频写入器,指定了输出文件名、视频编码器、帧率和分辨率。最后,循环遍历所有图像,并将其写入视频中,最终释放视频写入器。

   这样就完成了网络相机的实现,流程图如下:

AcoCoW3uD+W3AAAAAElFTkSuQmCC

三.实现过程

   1.下位机M1s Dock的功能实现:

      首先要修改内核代码,编译后重新烧录。因为在官方提供的SDK中,将目的ip固定了。需要手动修改,在下面目录:

      M1s_BL808_SDK/components/sipeed/e907/m1s_e907_xram/src/m1s_e907_xram_wifi.c

38x6IE+F7CAAAAAAElFTkSuQmCC

      此处的端口和ip被固定,需要修改为如下代码,才能让c906的函数传参到e907核心。

while (1) {
        vTaskDelay(100);
        printf("Socket connect..\r\n");
        if (0 > (sock = socket(AF_INET, SOCK_STREAM, 0))) {
            continue;
        }

        client_addr.sin_family = AF_INET;
        client_addr.sin_port = htons(private.port);//修改这里为传入端口
        client_addr.sin_addr.s_addr = inet_addr(private.ip);//修改这里为传入ip
        memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero));
        
        if(-1 == connect(sock,
                        (struct sockaddr *)&client_addr,
                        sizeof(struct sockaddr)))
        {
            closesocket(sock);
            continue;
        }

      之后重新编译e907目录下的firmware,用串口把编译后的firmware上传到M1s Dock。这样就可以上传图片流了。

      修改这个固件重新烧录后,便可以在主核心C906中编程,主函数代码如下:

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

    m1s_xram_wifi_init();
    m1s_xram_wifi_connect("taylor", "asdfghjkl");
    m1s_xram_wifi_upload_stream("192.168.243.41", 8888); //ip  port 
}

      项目中使用的摄像头为MIPI摄像头,通过调用bl_cam_mipi_mjpeg_init()函数进行摄像头初始化设置。在该函数中,会配置摄像头的分辨率、帧率和图像数据的压缩格式等参数,以及开启MIPI传输接口,使得摄像头能够向处理器传输数据。接下来,通过调用m1s_xram_wifi_upload_stream()函数,将摄像头采集到的图像数据上传到指定的IP地址和端口号。因此,该代码实现了通过摄像头采集图像,并通过WiFi上传到指定服务器的功能。

      跳转看一下m1s_xram_wifi_upload_stream()函数定义:

int m1s_xram_wifi_upload_stream(char *ip, uint32_t port)
{
    m1s_xram_wifi_t op = {0};
    strncpy(op.upload_stream.ip, ip, sizeof(op.upload_stream.ip));
    op.upload_stream.port = port;
    return m1s_xram_wifi_operation(&op, XRAM_WIFI_UPLOAD_STREAM);
}

      这段代码实现了将本地的流式数据上传到指定的IP地址和端口号的功能。函数m1s_xram_wifi_upload_stream接受两个参数,ip表示目标服务器的IP地址,port表示目标服务器的端口号。函数内部定义了一个结构体变量op,并使用strncpy将ip复制到结构体中的upload_stream.ip成员中,同时将port赋值给upload_stream.port。
最后,调用m1s_xram_wifi_operation函数并传入操作类型XRAM_WIFI_UPLOAD_STREAM和结构体变量op,以触发上传流操作。该函数的返回值为操作结果,可能是成功或失败。

2.上位机python基于tcp网络服务器实现接收图片

      使用Python的socket库监听本机8888端口,并等待客户端连接。

import socket
import os

def receive_images():
    # 创建服务端socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(('0.0.0.0', 8888))
    server_socket.listen()
    print("Server listening on 0.0.0.0:8888")

  当客户端连接成功后,服务端开始不断接收客户端发送的图片数据,并将其保存在out_put文件夹下。每张图片以imageN.jpg的形式进行命名,N代表保存的图片数量。当客户端连接异常中断或者传输的图片长度超出80000字节时,服务端会关闭连接。因为经过测试,偶尔接收的图片长度会出现一个特别大的数值,这个现象说明,主机已经错误的把其他数据解析为了图片长度,加上这个代码,会自己把不符合的文件不再接收,实现一个纠错。

# 记录接收图片的数量
    count = 1   

    while True:
        # 等待客户端连接
        client_socket, client_addr = server_socket.accept()
        print(f"Accepted connection from {client_addr}")

        while True:
            # 接收图片长度信息
            len_info = client_socket.recv(4)

            if not len_info:
                break

            length = int.from_bytes(len_info, byteorder='little', signed=True)
            print(f"Receiving image with length:{length}")

            # 发送确认信息
            client_socket.send(b's')

            # 接收图片数据
            data = b''
            while len(data) < length:
                chunk = client_socket.recv(length - len(data))
                if not chunk:
                    break
                data += chunk

            if len(data) != length:
                print("Image length exceeded limit, terminating connection")
                client_socket.close()
                break

            # 保存图片
            output_folder = 'output'
            if not os.path.exists(output_folder):
                os.makedirs(output_folder)
             #判断是否存在该文件,若存在则命名+1,防止文件被覆盖
            image_path = os.path.join(output_folder, f'image{count}.jpg')
            while os.path.exists(image_path):
                count += 1
                image_path = os.path.join(output_folder, f'image{count}.jpg')
            #二进制方式保存接受数据
            with open(image_path, "wb") as f:
                f.write(data)

            count += 1

        client_socket.close()

    server_socket.close()


if __name__ == '__main__':
    receive_images()

 

3.基于opencv实现jpg格式转换为视频

代码首先通过指定图像目录获取所有jpg格式的图片文件列表,并按文件名排序。接下来,代码定义了一个输出视频文件名和帧率。然后通过读取第一张图片,获取它的宽高并初始化视频写入器。最后,代码循环遍历所有的图片文件,将每张图片读取并写入视频中,并在最后释放视频写入器。这样,就可以将所有的图片合成一个视频文件。

import cv2
import os

# 设置图像目录
img_dir = 'out_picture/'

# 获取图像列表
images = [img_dir + f for f in os.listdir(img_dir) if f.endswith('.jpg')]
images.sort()

# 设置输出视频文件名
output_file = 'output.avi'

# 设置视频帧率
fps = 24

# 获取第一张图片的大小
frame = cv2.imread(images[0])
height, width, _ = frame.shape

# 初始化视频写入器
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
video_writer = cv2.VideoWriter(output_file, fourcc, fps, (width, height))

# 循环遍历图像并将其写入视频
for image in images:
    frame = cv2.imread(image)
    video_writer.write(frame)

# 释放视频写入器
video_writer.release()

四.遇到问题

      问题1:Linux环境下的编译环境搭建,克隆官方库的代码后,编译一直不成功,在交流群里求助,得到热心群友解答,然后删除了buildout的文件夹,重新编译才编译成功。

      问题2:运行连接wifi的函数时,在后台看不到M1s Dock 的连接,尝试了很久,最后是重新克隆代码,解决了这个奇怪的问题。

      问题3:解决了wifi连接后,M1s Dock作为客户端,拿到ip,但是连接不上服务器,咨询交流群群友,修改了firmware后,编译才能连接上主机。

      问题4:在linux环境编译firmware总是崩掉或者死机,在群里问群友才解决了,原来是线程过多,改小线程就完成了。

五.展望未来

      之后打算学习部署神经网络,在M1s Dock上跑图像识别和运用上语音识别关键字,bl8008性能非常好,很适合用来学习神经网络。然后也可以用M1s Dock上跑Linux的操作系统。总之,不会让这块板子吃灰了。

六.致谢

      真的衷心感谢电子森林提供了这样一个学习机会。在这个过程中,我学习了很多新的技能和知识。也特别感谢和我一起参与这个项目的群友,感谢他们帮助、分享经验,让学习过程更加愉快、高效。而且这个项目让我交到了一些新朋友。他们来自不同的背景和领域,但是我们都对学习和技术充满热情,这让我感到非常开心。我相信我们会在未来的学习和工作中相互支持、共同成长。

      这次经历让我收获了很多,感激不尽。我希望自己能够继续保持学习的热情和乐观的态度,不断提升自己的技能和能力,为自己和周围的人带来更多的价值。

附件下载
main.c
M1s Dock网络相机源文件
outvideo.py
基于opencv,将jpg文件转化视频
recvjpg.py
基于python的tcp服务器,接受图片
团队介绍
没有团队。或者说自己就是一个团队[doge]
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号