基于现有的技术限制,我们所看到的AR技术,基本上都是基于前景和背景两幅图像的合成,由于缺少深度感知系统,所以很难做到将背景图层中的物体叠加到前景图层中的物体前面来。所以本质上,我们所看到的就是两幅图像的合成,背景就是我们的摄像机信号,而前景叠加层就是由渲染引擎渲染出来的计算机CG画面,而且这个引擎渲染出来的画面还必须带有Alpha通道。
其动态画面的实现过程:
1.在具有实时渲染能力的计算机中制作我们所需要的CG画面,在渲染引擎的三维建模过程中,需要根据实际场景的物理尺度,制作对应尺度的虚拟物体。
2.在实际物理场景中,寻找一个坐标圆点,使其和三位软件中的虚拟坐标圆点吻合,然后将三维软件中的虚拟物体放置到对应的位置(在物理世界中,它看起来因该在的位置)。
3.现实物理世界的摄像机加装各种传感器,来采集物理摄像机的各种动作的物理参数(pan,tile,roll,X,Y,Z,Zoom,Focus)。
4.将采集到的摄像机的物理参数量化以后,通过协议传输给具有实时渲染能力的渲染引擎,渲染引擎再将接收到的数据赋值给三维软件中的虚拟摄像机,虚拟摄像机根据赋值后的虚拟位置,渲染对应的CG画面,然后由渲染引擎输出(这个过程主要是为了保证,显示世界和虚拟世界中的摄像机,采取同步的动作输出)。
5.将物理世界的摄像机的画面和渲染引擎的输出画面进行叠加,其结果就是我们看到的增强现实的画面。不过基于现在的主流技术,最后一步的画面合成都是在渲染引擎中完成的。
本次demo中,我们将给予Python的OpenCV库,来实现一个简单的图像合成过程。
在广电系统里面,实时的图像合成需求 ,主要就是在线包装和虚拟合成。在线包装的合成主要通过Fill和Key信号来完成,我们习惯上将其称作键。键的形式主要分为三种:硬键(hardKey),自键(selfKey),线性键(linear Key)。
硬键:
我们可以看作位图(biniry,非0即1),其合成后的画面即非此即彼,由黑白的Key信号决定了组成,key中黑色的部分即为原来的电视画面,Key中白色的部分就是要叠加的包装画面,常见与新闻类节目的新闻标题。
自键:
首先我们可以将包装的彩色画面转为黑白的灰度画面,此时的画面就只有灰阶,我们在选定一个灰阶参数,这个参数就是一个分界线,需要叠加的画面和不需要叠加的画面在此阴阳两隔。低于参数的在生成的Key信号中还是黑色,高于参数的在生成的Key信号中就是白色。之后的叠加和硬键过程类同。
线性键:
上面两个键的过程都是非此即彼,但是我们常常看到一些键是透明的,即带有Alpha通道,这就是线性键。key信号白色的叠加部分,取决于K值的选择,其画面叠加,我们可以简单类比为:(1-K)*底层画面亮度 + K* 前层画面亮度。当然其实际叠加过程远比这个复杂的多,但是大体上就是这个原理过程。
demo中展示的SRC为底层的信号,Fill为前层叠加信号,key为算法生成,我们可以通过Gray条框的滑动来设定自键的灰阶值,过程中我们可以看到key信号的变化,以及对应的最终合成画面的变化。
当我们将叠加画面变为虚拟物体时,就是一个简单的AR合成系统。
注意:1.我们选择的虚拟形象是自带Alpha通道的,所以我们将灰阶设为0.
2.我们输入选择的都是1080p的视频
3.背景视频为SRC,叠加视频为Fill,key和Mix为计算生成
import PySimpleGUI as sg #pip install pysimplegui
import cv2 #pip install opencv-python
import numpy as np #pip install numpy
#背景色
sg.theme('LightGreen')
#定义窗口布局
#Frame内元素
first_col_1 = [[sg.Image(filename='',size=(640,360),pad=(1,1),subsample=3,key='SRC')]]
first_col_2 = [[sg.Image(filename='',size=(640,360),pad=(1,1),subsample=4,key='Fill')]]
second_col_1 = [[sg.Image(filename='',size=(640,360),pad=(1,1),subsample=3,key='Mix')]]
second_col_2 = [[sg.Image(filename='',size=(640,360),pad=(1,1),subsample=3,key='Key')]]
col1= sg.Column([
[sg.Frame(title='SRC', layout=first_col_1 , title_location=sg.TITLE_LOCATION_BOTTOM, s=(650,378),element_justification='center'),
sg.Frame(title='Fill', layout=first_col_2 , title_location=sg.TITLE_LOCATION_BOTTOM, s=(650,378),element_justification='center')],
[sg.Frame(title='Mix', layout=second_col_1 , title_location=sg.TITLE_LOCATION_BOTTOM, s=(650,378),element_justification='center'),
sg.Frame(title='Key', layout=second_col_2 , title_location=sg.TITLE_LOCATION_BOTTOM, s=(650,378),element_justification='center')],
])
third_col_1 = [[ sg.Text('INPUT:'), sg.Input(s=(75,2)), sg.FileBrowse('...',initial_folder='.\\image',key='SRC_in')]]
third_col_2 = [[ sg.Text('INPUT:'),sg.Input(s=(75,2)), sg.FileBrowse('...',initial_folder='.\\image',key='Fill_in')]]
col2 = sg.Column([
[sg.Frame(title='SRC input:', layout=third_col_1 , title_location=sg.TITLE_LOCATION_TOP_LEFT, s=(650,45),element_justification='left'),
sg.Frame(title='Fill input:', layout=third_col_2 , title_location=sg.TITLE_LOCATION_TOP_LEFT, s=(650,45),element_justification='left')]
])
fouth_col_1 = [[sg.Text('Gray: '),sg.Slider((0, 255), 32, 1, orientation='h', size=(600, 15), key='gray_slider')]]
fouth_col_2 = [[sg.OK(), sg.Exit()]]
col3 = sg.Column([
[sg.Frame(title='GRAY:', layout=fouth_col_1 , title_location=sg.TITLE_LOCATION_TOP_LEFT, s=(650,75),element_justification='left'),
sg.Frame(title='Handle:', layout=fouth_col_2 , title_location=sg.TITLE_LOCATION_TOP_LEFT, s=(650,75),element_justification='right',vertical_alignment='bottom')]
])
#整合布局内容
layout = [[col1], [col2], [col3]]
window = sg.Window('OpenCV实时图像处理',
layout,
location=(800, 400),
finalize=True)
SRC_input = ''
Fill_input = ''
capture_src = cv2.VideoCapture(SRC_input)
capture_tlt = cv2.VideoCapture(Fill_input)
while True:
event, values = window.read(timeout=0, timeout_key='timeout')
#检查输入源是否改变
#print(values['SRC_in'],values['Fill_in'])
if values['SRC_in'] != SRC_input:
SRC_input = values['SRC_in']
capture_src = cv2.VideoCapture(SRC_input)
if values['Fill_in'] != Fill_input:
Fill_input = values['Fill_in']
capture_tlt = cv2.VideoCapture(Fill_input)
#fill 信号转key 信号的灰阶
grade_gray = values['gray_slider']
#实时读取图像
ret1, frame_left = capture_src.read()
ret2, frame_right = capture_tlt.read()
#GUI实时更新
if ret1:
frame_left = cv2.resize(frame_left,(640,360))
else:
frame_left = np.zeros((360,640,3) , np.uint8)
if ret2:
frame_right = cv2.resize(frame_right,(640,360))
else:
frame_right = np.zeros((360,640,3) , np.uint8)
img_src = frame_left
img_tlt = frame_right
if ret2:
img_tltgray = cv2.cvtColor(img_tlt, cv2.COLOR_BGR2GRAY)
ret1, mask = cv2.threshold(img_tltgray, grade_gray, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
img_src_bg = cv2.bitwise_and(img_src,img_src,mask=mask_inv) #fuse bg
img_tlt_fg = cv2.bitwise_and(img_tlt, img_tlt,mask=mask) #fuse fg
dst = cv2.addWeighted(img_src_bg, 1, img_tlt_fg, 1, 0) #fuse image
else:
dst = img_src
# fusion video
imgbytes1 = cv2.imencode('.png', dst)[1].tobytes()
window['Mix'].update(data=imgbytes1)
#fill video
imgbytes2 = cv2.imencode('.png', img_tlt)[1].tobytes()
window['Fill'].update(data=imgbytes2)
#source video
imgbytes3 = cv2.imencode('.png', img_src)[1].tobytes()
window['SRC'].update(data=imgbytes3)
# key video
if ret2:
imgbytes4 = cv2.imencode('.png', mask)[1].tobytes()
window['Key'].update(data=imgbytes4)
else:
imgbytes4 = cv2.imencode('.png', np.zeros((360,640,3) , np.uint8))[1].tobytes()
window['Key'].update(data=imgbytes4)
if event in (None ,'Exit'):
break
window.close()
效果图:
新闻标题示例:
AR合成示例: