很久没写文章了,最近闲来无事,又研究了下关于esp32_cam,之前也写过一篇文章,讲述了如何把视频流推送至公网服务器,但是用到的代码很不统一,esp32_cam端用的是c语言,服务器用的是nodejs,拉流客户端用的是python,看上去很别扭,更重要的是,虽然能推流至服务器,但传输效果也不是很理想,于是把三端的代码统一成了python,话不多说,直接上代码。
首先是esp32_cam端代码(使用thonny开发并烧录):
import socket
import network
import camera
import time
# 连接wifi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
wlan.connect('xxxx', 'xxxx') # 此处替换成自己的wifi和密码
while not wlan.isconnected():
pass
print('网络配置:', wlan.ifconfig())
# 摄像头初始化
try:
camera.init(0, format=camera.JPEG)
time.sleep(0.5)
except Exception as e:
camera.deinit()
# camera.init(0, format=camera.JPEG)
# 其他设置:
# 上翻下翻
camera.flip(1)
#左/右
camera.mirror(1)
# 分辨率
camera.framesize(camera.FRAME_CIF)
# 选项如下:
# FRAME_96X96 FRAME_QQVGA FRAME_QCIF FRAME_HQVGA FRAME_240X240
# FRAME_QVGA FRAME_CIF FRAME_HVGA FRAME_VGA FRAME_SVGA
# FRAME_XGA FRAME_HD FRAME_SXGA FRAME_UXGA FRAME_FHD
# FRAME_P_HD FRAME_P_3MP FRAME_QXGA FRAME_QHD FRAME_WQXGA
# FRAME_P_FHD FRAME_QSXGA
# 有关详细信息,请查看此链接:https://bit.ly/2YOzizz
# 特效
camera.speffect(camera.EFFECT_NONE)
#选项如下:
# 效果\无(默认)效果\负效果\ BW效果\红色效果\绿色效果\蓝色效果\复古效果
# EFFECT_NONE (default) EFFECT_NEG \EFFECT_BW\ EFFECT_RED\ EFFECT_GREEN\ EFFECT_BLUE\ EFFECT_RETRO
# 白平衡
# camera.whitebalance(camera.WB_HOME)
#选项如下:
# WB_NONE (default) WB_SUNNY WB_CLOUDY WB_OFFICE WB_HOME
# 饱和
camera.saturation(0)
#-2,2(默认为0). -2灰度
# -2,2 (default 0). -2 grayscale
# 亮度
camera.brightness(0)
#-2,2(默认为0). 2亮度
# -2,2 (default 0). 2 brightness
# 对比度
camera.contrast(0)
#-2,2(默认为0).2高对比度
#-2,2 (default 0). 2 highcontrast
# 质量
camera.quality(30)
#10-63数字越小质量越高
# socket TCP 的创建
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('xxx.xxx.xxx.xxx', xxxx)) # 此处替换成自己的公网ip和端口号(端口号是自定义)
time.sleep(0.5)
try:
while True:
buf = camera.capture() # 获取图像数据
len_buf = len(buf) # 发送数据长度
s.sendall(str.encode('%-10s' % len_buf))
s.sendall(buf) # 向服务器发送图像数据
# time.sleep_ms(50)
except Exception as e:
print(e)
camera.deinit()
finally:
camera.deinit()
需要替换的有两处,wifi名称和密码以及公网ip和端口号。
然后上服务器端代码:
import socket
import time
import cv2
import io
from PIL import Image
import numpy as np
import threading
cc = [] # 用来保存已经连接到服务器的摄像头socket
cp = [] # 用来保存已经连接到服务器的电脑socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", xxxx)) # 此处填写自定义的端口号,注意,要跟esp32_cam中定义的端口号一致
s.listen(10)
def receive_all(sock, count):
buf = b''
while count:
recv_data_temp = sock.recv(count)
if not recv_data_temp:
return None
buf += recv_data_temp
count -= len(recv_data_temp)
return buf
def client_camera(clientsocket):
global cp
global cc
print('摄像头已连接')
while True:
# 注释调两个if语句后,视频流会相对流畅一些
if clientsocket.recv(1024) == b'':
print('摄像头已退出')
cc = []
break
if len(cp) == 0:
time.sleep(1)
continue
try:
length = receive_all(clientsocket, 10)
data = receive_all(clientsocket, int(length)) # 目前只设计为一个摄像头和一台pc
len_buf = len(data) # 发送数据长度
cp[0].sendall(str.encode('%-10s' % len_buf))
cp[0].sendall(data)
except Exception as e:
pass
# print(f'摄像头以退出:{e}')
def client_pc(clientsocket):
global cp
while True:
time.sleep(1)
if clientsocket.recv(1024) == b'':
print('客户端已退出')
cp = []
break
while True:
# 接收所有连接,每受到一个客户端,就启动一个新线程
clientsocket, addr = s.accept()
try:
if clientsocket.recv(1024).decode('utf-8') == 'client': # 说明该消息是客户端pc发送的拉流请求
cp.append(clientsocket)
print(cp)
threading.Thread(target=client_pc, args=(clientsocket,)).start()
else: # 说明是摄像头推流的请求
cc.append(clientsocket)
threading.Thread(target=client_camera, args=(clientsocket,)).start()
print(cc)
except Exception as e:
clientsocket.close()
此处要修改的是端口号,要跟esp32_cam中自定义的端口号一致,其它的看注释即可
最后上拉流的客户端代码:
import socket
import cv2
import io
from PIL import Image
import numpy as np
import time
def receive_all(sock, count):
buf = b''
while count:
recv_data_temp = sock.recv(count)
if not recv_data_temp:
return None
buf += recv_data_temp
count -= len(recv_data_temp)
return buf
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.connect(('xxx.xxx.xxx.xxx', xxx)) # 此处设置公网的ip地址以及自定义的端口号
s.send('client'.encode('utf-8'))
time.sleep(0.5)
while True:
try:
length = receive_all(s, 10)
data = receive_all(s, int(length))
bytes_stream = io.BytesIO(data)
image = Image.open(bytes_stream)
img = np.asarray(image)
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) # ESP32采集的是RGB格式,要转换为BGR(opencv的格式)
cv2.imshow("ESP32 Capture Image", img)
if cv2.waitKey(1) == ord("q"):
break
except Exception as e:
print(e)
此处要修改的是公网ip和自定义的端口号
目前代码如果都跑在局网内,效果还是很不错的,我使用的公网带宽是3m,效果嘛你们自己部署后自己看吧,有时间我还会再优化这一套流程,如果有好想法的小伙伴也可以给我留言一起讨论。