一、概述
本代码经实跑,在01Studio的K230开发板上能够正常运行,其主要功能包括:建立WiFi热点(名称K230
,密码12345678
)、在IDE
和LCD
上显示拍摄到的视频,支持浏览器通过192.168.1.88
查看视频,同时也请大侠们帮忙搭一个html
的框架,在不需要APP的条件下遥控车、船或者飞机。
实际运行效果:浏览器和IDE显示
实际运行效果:WiFi热点
实际运行效果:手机浏览器查看视频
二、代码功能介绍
- 代码中包含了三个主要线程:
三个线程间共用图像img
,用锁imgLock
防止访问冲突
th_Camera()
:定时拍摄照片,并将照片存储在img
变量中,供其他线程处理和使用。th_Display()
:显示img变量里面的图像。http_server()
:创建WiFi
热点并建立HTTP
服务器,推送视频图像至客户端浏览器。
- 在
__main__
函数中,启动了三个线程分别执行上述三个功能函数。
三、待改进的问题
WiFi
热点和通讯
-
画面会自己停止
推送画面帧数从来没超过 4000 帧,最多约 6 分钟左右视频就会停止。
画面停止时系统报错Error No 11
不知道是硬件问题,还是底层驱动问题,也可能是内存占用问题,或者浏览器缓存问题。 -
无法再次绑定80端口
通讯失败后,我试图重新启动WiFi
热点,在重启HTTP
服务时无法绑定80
端口
可能是socket.close
函数没有释放端口资源 -
多设备接入
正常情况下,WiFi
和HTTP
服务应该允许接入多个设备的。
应该在每一次accept
成功后,建立一个对应的HTTP
服务线程,访问结束就关闭线程。
但我注意到,有两个设备接入热点时,之前访问成功的浏览器视频明显出现卡顿,所以就没做。 -
客户端
IPv4
地址
socket.accept
函数得到的client_addr
我没能解析成形如192.168.1.20
的IPv4
地址。
-
缺少
DHCP
服务
硬件或者底层似乎有多设备接入的IP
地址分配能力,但IP
地址局限于192.168.1.20
之后。 -
HTTP
服务
没做HTTP
协议的应答和解析,通过浏览器仅能看视频,无法控制设备。 -
视频编码
本程序是按jpeg
格式推送图片的,若修改为视频码流模式帧率会高很多。比如 H265 编码视频格式,并打包进http
协议命令里,发送给 3 个视频框。
四、协作需求
作为遥控车 / 船 / 飞机的控制界面,需要定义一个 HTML 框架,里面应包含以下内容:
- 云台控制 6 组:上 / 下 / 左 / 右 / 远 / 近,幅度(输入框 + 滚动条,与每个按钮对应)。
- 运动控制 4 组:进 / 退 / 左 / 右,速度 / 幅度(输入框 + 滚动条,与每个按钮对应)。
- 视频框 3 个:前摄 / 左摄 / 右摄。受限于网速,分辨率应该不超过 640x480(GC2093 的长宽比 为1920:1080,满足这个条件的图像才不会畸变)。K230 支持的 3 个摄像头,可以用作视觉摄像,也可分别为视觉、深度、热成像。
请各位大侠帮忙一起动手,看看能否解决上述问题;同时欢迎大家参与和探讨,寻找更好的方法。
请大神们赐教,一起解决这些问题,让 K230 的应用更加完善!
from media.sensor import *
from media.display import *
from media.media import *
import utime,ubinascii,ujson,usocket,_thread,network,gc
#http服务
def http_server():
global img,imgLock,RunCamera,ssCNT
img = None #拍摄到的照片,多线程运行时防未赋值出错
RunCamera = True #线程退出条件,比如按Key键3秒后修改此值即可关闭程序
while img==None and RunCamera==True: #死等摄像头启动后赋值给img
os.exitpoint()
utime.sleep_ms(100)
while RunCamera: #线程退出条件,比如按Key键3秒后修改此值即可关闭程序
os.exitpoint()
try:
# 创建WiFi热点
# 待改进:
# 不知道是硬件还是底层驱动问题,没超过4000帧画面就会停止----------
# 通讯失败后重新启动WiFi热点,试图解决WiFi通讯中断的问题
print('\tCreate WiFi hostpot with (ssid=K230,key=12345678)')
ap = network.WLAN(network.AP_IF)
ap.active(True)
ap.config(essid='K230', password='12345678')
# 缺DHCP服务
# 硬件或者底层似乎有多设备接入的IP地址分配能力,但IP地址局限于192.168.1.20之后
# 有两个设备接入热点时,已经打开的终端浏览器的帧率明显降低
ap.ifconfig(('192.168.1.88', '255.255.255.0', '',''))#配置本机IP为非路由器常用的192.168.1.1
# 创建 HTTP 服务器
IPaddress = ap.ifconfig()[0]
Port = 80
ai = usocket.getaddrinfo(IPaddress, Port)
addr = ai[0][-1]
print(f'\tCreate HTTP server listen at {IPaddress}:{Port}\n\n')
s = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM)
s.bind(addr) #再次绑定端口时会报错,估计底层驱动有错,关闭socket时没有释放端口
s.listen(5)
except Exception as e:
print(f"\n\t建立HTTP服务时出错:{e}")
continue
while RunCamera: #线程退出条件,比如按Key键3秒后修改此值即可关闭程序
os.exitpoint()
try:
cl, addr = s.accept() #没能理解addr的表达方法,未见形如192.168.1.20的IPv4的地址数据
ssCNT = 0 #本次成功发送的帧计数器清零,-------实测未超过4000帧就不通讯了---------------
request = cl.recv(1024)
if not request:
continue
request_str = request.decode()
#待改进:
# 未对HTTP协议进行解析和应答,无法通过浏览器控制设备
# 应使用形如 if 'GET /stream' in request_str:的命令解析HTTP指令
#需要做的工作:
# 作为遥控车/船/飞机,应该定义一个html框架,里面应包含以下内容:
# 云台控制 6组:上/下/左/右/远/近,幅度(输入框+滚动条,与每个按钮对应)
# 运动控制 4组:进/退/左/右,速度/幅度(输入框+滚动条,与每个按钮对应)
# 视 频 框 3个:前摄/左摄/右摄
# 分辨率640x360(GC2093使用比例1920:1080,图像才无畸变)
# K230支持的3个摄像头,也可分别为视觉、深度、热成像
# 解析http协议的GET/POST命令,并执行相应的指令
header = "HTTP/1.1 200 OK\r\n" \
"Server: Tao\r\n" \
"Content-Type: multipart/x-mixed-replace;boundary=Tao\r\n" \
"Cache-Control: no-cache\r\n" \
"Pragma: no-cache\r\n\r\n"
# ********* 这个http内容来自网友Tao **********
cl.send(header)
except Exception as e:
print(f"\n\tHTTP错误:{e}")
break
#被前面的accept和recv阻塞了
# 若无阻塞,下面的代码不需要单独做循环,至少可跟recv放进同一个循环里
while RunCamera: #线程退出条件,比如按Key键3秒后修改此值即可关闭程序
os.exitpoint()
try:
if imgLock.acquire(1,1): #申请img变量的锁,阻塞1秒
img_bytes = img.compress(quality=50)
imgLock.release()
# ********* 这个http内容来自网友Tao **********
header = f"--Tao\r\nContent-Type: image/jpeg\r\nContent-Length: {len(img_bytes)}\r\n\r\n"
#待改进
# 按图片格式推送jpeg图片
# 实际上按视频码流模式的帧率会高很多
#需要做的工作
# 按视频流格式编码,比如H265视频格式
# 打包进http协议里,发送给3个视频框
cl.send(header)
cl.send(img_bytes)
#摄像头0的推送
#摄像头1的推送
del img_bytes
gc.collect()
else:
print('img变量锁申请超时')
except Exception as e:
#if b'[Errno 11] EAGAIN' in e:
if e.errno == 11:
print('\t-----Error 11-----')
break
else:
print(f"\n\tHTTP错误:{e}")
break
utime.sleep_ms(100) #限制帧率不超过10,给其它线程留下CPU时间
ssCNT += 1 #帧计数---------------------------
cl.close()
s.close()
ap.active(False)
print('\t----- Restart HTTP server -----')
utime.sleep(5) #等5秒,重启WiFi
#拍摄
# 定时拍摄照片到img变量
# 其它线程可以使用或修改img变量,获得图像能力
def th_Camera():
global img,imgLock,RunCamera,ssCNT
cam = Sensor() #默认摄像头2,一共支持3个摄像头
cam.reset()
cam.set_framesize(width=640, height=360, chn = CAM_CHN_ID_0) #每个摄像头有3个通道,我们使用ch0
cam.set_pixformat(Sensor.RGB565, chn = CAM_CHN_ID_0) #目前还不支持rgb888转换到jpg格式
#摄像头0配置
#摄像头1配置
Display.init(Display.ST7701, to_ide=True, osd_num = 2)
MediaManager.init()
cam.run()
RunCamera = True
clock = time.clock()
fps = 0
ssCNT = 0 #计时------------------------------
while RunCamera: #线程退出条件,比如按Key键3秒后修改此值即可关闭程序
clock.tick()
os.exitpoint()
if imgLock.acquire(1,1): #申请变量img的锁,阻塞1秒
del img
gc.collect()
img = cam.snapshot()
img.draw_string_advanced(5,5,36,'%d fps: %.1f'%(ssCNT,fps),color=(255,0,0))
#摄像头0的拍摄
#摄像头1的拍摄
imgLock.release()
utime.sleep_ms(50) #为保证数据及时刷新,2倍于推送帧率
fps = clock.fps()
cam.stop()
utime.sleep_ms(100)
MediaManager.deinit()
#显示,其它线程可以修改img变量获得显示能力
def th_Display():
global img,imgLock,RunCamera
RunCamera = True #线程退出条件,比如按Key键3秒后修改此值即可关闭程序
img = None
while img == None and RunCamera==True: #死等摄像头启动后赋值给img
os.exitpoint()
utime.sleep_ms(100)
while RunCamera: #线程退出条件,比如按Key键3秒后修改此值即可关闭程序
os.exitpoint()
if imgLock.acquire(0,0):#申请变量img的锁,无阻塞
Display.show_image(img,x=80,y=60) #显示图片
#显示摄像头0的图像
#显示摄像头1的图像
imgLock.release()
utime.sleep_ms(100)
if __name__ == "__main__":
global RunCamera
RunCamera = True #线程退出条件,比如按Key键3秒后修改此值即可关闭程序
imgLock = _thread.allocate_lock() #线程锁实例
_thread.start_new_thread(th_Camera, ()) #摄像头线程
_thread.start_new_thread(th_Display, ()) #显示线程
_thread.start_new_thread(http_server, ()) #推流线程
while RunCamera:
os.exitpoint()
utime.sleep(1)