使用ipywebrtc组件,获取本地视频流并传输到远程Jupyter服务器,由服务器处理过视频后再回传到本地,最后由ipywidgets.Image
组件展示。
建议使用Chrome浏览器
前往官方示例并启用摄像头,即可体现实际效果。
如果你想更深刻地体验示范代码,可以去Binder打开任意一个.ipynb
文件,一步步运行即可。
需要先安装ipywebrtc
组件,有两种方法,简单的办法是直接通过pip
安装(需要jupyter版本在5.3及以上):
pip install ipywebrtc
第二种是通过github安装最新预览版:
git clone https://github.com/maartenbreddels/ipywebrtc
cd ipywebrtc
pip install -e .
jupyter nbextension install --py --symlink --sys-prefix ipywebrtc
jupyter nbextension enable --py --sys-prefix ipywebrtc
如果你使用的是jupyter lab,那么只需要在终端运行以下语句:
jupyter labextension install jupyter-webrtc
完成准备工作后,首先需要在Jupyter文件中引用ipywebrtc
库,然后创建一个流,可用的流请见下文组件介绍部分,这里以CameraStream
为例,使用本地前置摄像头:
from ipywebrtc import CameraStream
camera = CameraStream.facing_user(audio=False)
camera
不出意外的话,Chrome浏览器会弹窗询问是否允许网页使用摄像头,选择允许后,就可在输出区看到摄像头拍到的视频了。
如果Chrome不弹出提示,而是显示
Error creating view for media stream: Only secure origins are allowed
,则表示浏览器认为当前网站不安全(没有使用https
连接),因此禁用了摄像头。最简单的解决方法是在Chrome快捷方式的“目标”一栏最后加上--unsafely-treat-insecure-origin-as-secure="http://host_ip:port"
并重启即可(把host_ip:port
修改为自己的服务器地址)
但这时候,视频还只是在本地显示,并没有上传到Python内核所在的服务器中(假设你使用的是远程服务器),因此也就没办法在Python上下文中获取视频内容。
所以下一步,我们需要创建一个ImageRecorder
来记录流媒体,并以图片的形式发送给服务器上的Python内核:
from ipywebrtc import ImageRecorder
image_recorder = ImageRecorder(stream=camera)
image_recorder
运行这段代码,会显示ImageRecorder
组件,点击组件上的相机图标,即可抓拍来自stream
的画面。
之后,再通过访问image_recorder.image.value
并转换成Pillow
格式,即可在Python内获取该图片:
import PIL.Image
import io
im = PIL.Image.open(io.BytesIO(image_recorder.image.value))
如果不需要处理,仅需要预览,则直接显示image_recorder.image
即可,它是一个ipywidgets.Image
组件,本身就有展示图片的功能。
如果需要使用opencv-python
处理图片,可以通过canvas = numpy.array(im)[..., :3]
得到cv2
能处理的图像数组,处理之后可以在matplotlib
中展示图片,但我更推荐使用ipywidgets.Image
组件:
from ipywidgets import Image
out = Image()
out.value = cv2.imencode('.jpg', cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB))[1].tobytes() # canvas出处见上文说明部分
out
或者如果你不希望再引入cv2
,也可以按照官方给出的方式:
from ipywidgets import Image
out = Image()
im_out = PIL.Image.fromarray(canvas)
f = io.BytesIO()
im_out.save(f, format='png')
out.value = f.getvalue()
out
目前我们仅介绍了怎么抓取一张图片,处理并展示,这些都可以很容易地通过官方文档学会。那么我们怎样才能连续不停地抓取一个视频里的各个帧,对其处理后再一一展示呢?下面将介绍我总结出来的ImageRecorder
连续抓取的方法。
之所以连续抓取依然使用
ImageRecorder
而不是VideoRecorder
,是因为VideoRecorder
抓取视频必须有开始和结束,只有结束后才能对抓取的整个视频片段做处理,这达不到“实时”的要求。
我分析了作者的代码后发现,并没有类似于recorder.record()
或recorder.take_photo()
之类的函数,而ImageRecorder
前端抓取到下一帧图像后,会通知Python内核把ImageRecorder
的recording
属性设置为True
,然后在抓取一张图片后,recording
属性会自动变为False
。因此我就想尝试一种循环机制,在每次处理完上一帧图片后,自动设置recording
为True
以抓取下一帧图片。
然而经过实验,使用while
循环或for
循环都是行不通的,这大概是因为Jupyter的前端是由Javascript渲染,单纯在后端Python环境下改变属性而不通知前端,是无法继续抓取的。
这个原因仅是我的个人猜测,因为时间原因没有去细看前端文件与底层逻辑,所以理解可能不正确,欢迎在评论区指正。
最后参考了Github上martinRenou的帖子,想到了下面这种模式,来完成连续的实时视频处理:
import io
import PIL.Image
import numpy as np
from ipywidgets import Image, VBox, HBox, Widget, Button
from IPython.display import display
from ipywebrtc import CameraStream, ImageRecorder
VIDEO_WIDTH = 640 # 窗口宽度,按需调整
VIDEO_HEIGHT = 480 # 窗口高度,按需调整
camera = CameraStream(constraints=
{
'facing_mode': 'user',
'audio': False,
'video': {
'width': VIDEO_WIDTH, 'height': VIDEO_HEIGHT}
}) # 另一种CameraStream创建方式,参考下文组件介绍部分
image_recorder = ImageRecorder(stream=camera)
out = Image(width=VIDEO_WIDTH, height=VIDEO_HEIGHT)
FLAG_STOP = False # 终止标记
def cap_image(_): # 处理ImageRecord抓取到的图片的过程
if FLAG_STOP:
return # 停止处理
im_in = PIL.Image.open(io.BytesIO(image_recorder.image.value))
im_array = np.array(im_in)[..., :3]
canvas = process(im_array) # process是处理图像数组的函数,这里没写出来,各位按处理需要自己写即可
im_out = PIL.Image.fromarray(canvas)
f = io.BytesIO()
im_out.save(f, format='png')
out.value = f.getvalue()
image_recorder.recording = True # 重新设置属性,使ImageRecorder继续抓取
# 注册抓取事件,参考我另一篇Blog:https://qxsoftware.blog.csdn.net/article/details/86708381
image_recorder.image.observe(cap_image, names=['value'])
# 用于停止抓取的按钮
btn_stop = Button(description="Stop",
tooltip='click this to stop webcam',
button_style='danger')
# btn_stop的处理函数
def close_cam(_):
FLAG_STOP= True
Widget.close_all()
btn_stop.on_click(close_cam) # 注册单击事件
# Run this section and Press the Camera button to display demo
display(VBox([HBox([camera, image_recorder, btn_stop]), out]))
在Jupyter中运行这段代码后,会出现一个本地camera预览框、一个ImageRecorder
抓取框、一个红色的Stop按钮,以及一个尚无图片的Image
组件。
点击ImageRecorder
上的相机按钮,会激活cap_image
函数,之后处理过的图片就会在Image
组件里展示,接下来直到点击Stop,这个过程都会重复进行。
只有在点击相机之后,更后面的Jupyter Cells才能正常访问image_recorder.image
,不然会报错OSError: cannot identify image file
。
这里最关键的部分,就是在
ImageRecorder.image
的observer
事件注册函数里添加的image_recorder.recording = True
语句。至于为何在注册函数外添加这句话无效,就需要研究一下前后端的联系了。
需要注意的是,注册函数cap_image
里出现的Error不会引发中断,因此如果运行后Image
组件不显示图像,可能是cap_image
发生了Error,这可以通过把cap_image
里的内容提出来放到新的Cell中去验证。
ipywebrtc
中,可用的流媒体有:
VideoStream
:可以由VideoStream.from_file(path)
获取本地视频,或由VideoStream.from_url(url)
获取网络视频
CameraStream
:通过本地摄像设备或者网络摄像头(webcam)获取媒体流,创建方法有两种:
camera = CameraStream(constraints=
{
'facing_mode': 'user', # 'user表示前置摄像头,'environment'表示后置摄像头
'audio': False, # 是否同时获取音频(需要硬件支持)
'video': {
'width': 640, 'height': 480 } # 获取视频的宽高
})
front_camera = CameraStream.facing_user(audio=False)
:前置摄像头back_camera = CameraStream.facing_environment(audio=False)
:后置摄像头AudioStream
:音频流,与VideoStream
的创建方式相同
WidgetStream
:通过WidgetStream(widget=target)
指定widget
,可以将任何ipywidget
实例的输出创建为媒体流
这些媒体流都继承自
MediaStream
类
除流媒体组件外,还有记录器组件,用于记录前端Javascript获取到的流并发送到Python内核,其中ImageRecorder
已经在示例里介绍得很详细,而VideoRecorder
用法与之类似,可参阅官方文档。
最后还有ipywebrtc.webrtc
中的组件,经过测试目前还有些BUG,可参考Chat视频聊天室。
更多内容(如ipyvolumn
系列)会在以后更新。
原创于CSDN,转载请注明出处:https://qxsoftware.blog.csdn.net/article/details/89513815。有任何问题或想法请在下方留下评论~~