基于Sipeed M1s Dock完成网络相机
一、硬件准备:
Sipeed M1s-Dock开发板基于BL808芯片,内部包含多个RISC-V处理器,最高时钟频率达480MHz,其中包含一颗RISC-V 32-bit高性能CPU,集成Wi-Fi/BT/Zigbee无线子系统,可以实现多种无线连接和数据传输,提供多样化的连接与传输体验。
M1S Dock 搭载有两个 TypeC 接口。默认情况下 UART 口用做于电脑和 M1S Dock 的串口通信,OTG 口默认用于给板子上的 BL808 芯片里面的 C906 核心烧录固件。
上图中板子的左下角有 UART 口,默认有 USB 转双串口功能连接到 BL808 芯片的两个核心上;右下角有 OTG 口,M1s Dock 默认固件将它作用于烧录 C906 核心的固件。
C906是一款超高能效处理器,兼容RV64IMA[FD]C[V] 指令集,是业界最早量产的向量扩展RISC-V指令集处理器。
二、对于项目的理解与程序编写思路:
本项目首先进行初始化C906核心编程,驱动开发板的摄像头与Wi-Fi模块,将电脑与开发板连接到同一个网络环境下,通过电脑端使用python语言编写的socket接受程序,将接受到的图片放入指定文件夹中,在图像接收结束后运行电脑端程序,将拍摄的图片输出为.avi的视频文件,实现网络相机功能。
三、图像接收部分编程
1、开发板C906编程:
根据所给例程,驱动Sipeed M1s Dock开发板的相机模块和wifi模块,为电脑端接受图像作必要的硬件准备。
编译好的程序和bin文件
2、电脑端编程:
首先是Socket 建立和照片接受部分如下:
import socket
import os
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8888))
server.listen()
cnt = 1 #记录图片张数
while True:
client, source = server.accept()
while True:
len_jpg = client.recv(4) #接受发过来的图片长度
if not len_jpg:
break
length = int.from_bytes(len_jpg, byteorder='little', signed=True)
print(f"receiving length: {length}")
client.send(b's')
if length > 80000:
print("receive wrong,restart client")
client.close()
break
# 接收图片数据
data = b''
while len(data) < length:
chunk = client.recv(length - len(data))
if not chunk:
break
data += chunk
# 保存图片
out_folder = 'receiving_picture'
if not os.path.exists(out_folder):
os.makedirs(out_folder)
image_file = os.path.join(out_folder, f'image{cnt}.jpg')
while os.path.exists(image_file):
cnt += 1
image_file = os.path.join(out_folder, f'image{cnt}.jpg')
with open(image_file, "wb") as f:
f.write(data)
cnt += 1
client.close()
server.close()
程序总体功能解释:主要实现是Python Socket 服务器,它监听端口 8888 上的所有连接并接收来自客户端的图像数据。
程序的主要功能详细解释:
- import socket 和 import os 语句导入了 Python 内置的 socket 模块和 os 模块。
- 创建了一个 socket 对象,使用 AF_INET 和 SOCK_STREAM 参数,代表使用 IPv4 协议和 TCP 协议。然后使用 bind() 方法将其绑定到本地地址 0.0.0.0 和端口 8888 上,最后使用 listen() 方法开始监听连接请求。
- 定义一个计数器 cnt 来记录接收到的图像数量。
- 进入一个无限循环,使用 accept() 方法接收来自客户端的连接请求,并返回一个包含客户端地址和端口的元组 source 和一个新的 socket 对象 client。
- 进入一个新的无限循环,首先使用 recv() 方法接收来自客户端的前四个字节数据,这些数据表示接下来要发送的图像数据的长度。使用 int.from_bytes() 方法将接收到的字节转换为整数值,这里使用 little-endian 字节序,即低位字节在前,高位字节在后。
- 发送一个字节 b's' 给客户端,作为确认消息,表示服务器已准备好接收数据。
- 如果接收到的图像数据长度大于 80000 字节,则认为出现错误,输出错误信息并关闭客户端连接。
- 如果长度没有超过限制,则使用一个循环来接收图像数据,直到数据长度等于接收到的长度。由于 recv() 方法只能接收固定长度的数据,因此需要使用一个循环来接收完整的数据。
- 接收完整的图像数据后,创建一个名为 receiving_picture 的文件夹(如果该文件夹不存在),然后保存图像文件到该文件夹中,并将文件命名为 imageX.jpg,其中 X 表示计数器的值。
- 将计数器 cnt 的值加 1。
- 当接收到的数据为空时,跳出内层循环并关闭客户端连接。
- 当程序退出内层循环时,关闭客户端连接并回到外层循环,等待下一个客户端连接。当需要退出服务器程序时,使用 server.close() 方法关闭服务器的套接字连接。
四、将记录的照片整理输出为视频
程序总体功能解释:使用OpenCV库将一组JPEG格式的图片合成为一个AVI视频文件。
import cv2
import os
def main():
# 设置图像目录
input_folder = 'receiving_picture/'
# 获取图像列表
images = [os.path.join(input_folder, f) for f in os.listdir(input_folder) if f.endswith('.jpg')]
images.sort()
# 设置输出视频文件名
output_file = 'receiving_picture.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()
if __name__ == '__main__':
main()
程序功能详细解释:
import cv2
import os
导入OpenCV库和os库。
def main():
定义一个名为main()的函数,这是整个程序的入口。
# 设置图像目录
input_folder = 'receiving_picture/'
设置输入图片所在的目录为receiving_picture。
# 获取图像列表
images = [os.path.join(input_folder, f) for f in os.listdir(input_folder) if f.endswith('.jpg')]
images.sort()
获取该目录下所有以.jpg结尾的文件名,将它们按字典序排序,得到一个包含所有图片文件名的列表images。
# 设置输出视频文件名
output_file = 'receiving_picture.avi'
设置输出视频的文件名为receiving_picture.avi。
# 设置视频帧率
fps = 24
设置输出视频的帧率为24。
# 获取第一张图片的大小
frame = cv2.imread(images[0])
height, width, _ = frame.shape
使用OpenCV库的imread函数读入第一张图片,然后获取它的高度和宽度,并赋值给height和width变量。
# 初始化视频写入器
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
video_writer = cv2.VideoWriter(output_file, fourcc, fps, (width, height))
使用OpenCV库的VideoWriter_fourcc函数设置视频的编码格式为MJPG,然后初始化一个VideoWriter对象video_writer,它用于将图片写入视频。
# 循环遍历图像并将其写入视频
for image in images:
frame = cv2.imread(image)
video_writer.write(frame)
循环遍历所有图片文件,使用OpenCV库的imread函数读入每张图片,然后使用VideoWriter对象的write方法将该帧图片写入视频。
# 释放视频写入器
video_writer.release()
最后,使用VideoWriter对象的release方法释放资源,完成视频写入操作。
if __name__ == '__main__':
main()
程序的主入口,当该脚本被作为主程序执行时,调用main()函数。
程序成功运行结果演示如下:
Socket 端等待连接
启动电脑服务端,连接成功,传输照片
接收到的图片
生成的视频
五、项目中遇到的问题与解决方案
在项目中主要遇到的问题是开发板上的网络与电脑的网络建立的时候需要一个socket协议握手,在此之前未接触过此协议,包括后续的网络连接和信息的传送都需要使用到。通过大量查阅相关资料,了解到socket是在通信时,其中一个网络应用程序将一段信息写入其所在主机的socket,通过连接到网络接口卡(NIC)的传输介质将该信息发送到另一台主机的socket,以便对方能够接收。这一块的实现都是利用python编程完成,查阅了例程代码,借鉴了大量的前人经验以及库函数,根据项目需求进行改变适用。
六、未来改进方向
1、视频生成自动化
目前实现的方案中,需要在电脑端接收到一定量的图像后再停止接收,然后人为操作生成视频。为减少用户操作,更好适应网络相机的需求,考虑尝试将视频生成自动化。程序在识别到接收了足够多的图像后便开始不间断生成视频,以一个固定时长为单位逐个生成视频。
2、双工通信
当前项目中的信息传递全部为下位机发送至上位机,可以通过更改C906 Wi-Fi传输的驱动程序,使之能够接收到上位机发送的控制指令,如图像采样间隔等。