隐私遮蔽
- 高速球摄像机实现隐私区域遮蔽
- 隐私区域自由选取
- 无论相机处于什么状态,遮蔽块能随着球机移动而移动
- 球机在不同位姿下的图像点坐标的实时匹配
原理推导
-
目前两种思路:一种是按照3D定位原理匹配;一种是利用共同世界坐标的单应变换;
-
球机坐标转换关系推导一下,理论验证通过,实用的话暂时没想好方法;
-
3D定位原理类似,参照《球机3D定位解析及ONVIF实现》;
单应变换思路
-
可以转化为球机在旋转或变倍过程中的,不同位姿下的图像坐标匹配关系推导如下
-
相机1时刻,图像坐标和相对世界坐标转换关系
-
相机2时刻,图像坐标和相对世界坐标转换关系
-
将(1)式代入(2)式,可得
- 化简,得
式中,
- 求解,可得
- 球机在不同位姿下的转换,需要内参矩阵(标定可得)、旋转和平移矩阵(球机的PTZ轴不在透镜中心,所以根据PTZ的变化,无法获知旋转和平移矩阵)
3D定位思路
- 示意图如下,目标点在球机两个不同位姿下的图像坐标
P1
和P2
- 先计算 P 1 P_1 P1到视野中心点,球机旋转的水平和垂直角度
{ α V : 1 − > o = a r c t a n ( Y 1 − H / 2 H / 2 ∗ t a n ( F O V v / 2 ) ) α H : 1 − > o = a r c t a n ( X 1 − W / 2 W / 2 ∗ t a n ( F O V h / 2 ) ) \begin{cases} \alpha_{V:1->o}=arctan(\frac{Y_1-H/2}{H/2}*tan(FOV_v/2)) \\ \alpha_{H:1->o}=arctan(\frac{X_1-W/2}{W/2}*tan(FOV_h/2)) \end{cases} {αV:1−>o=arctan(H/2Y1−H/2∗tan(FOVv/2))αH:1−>o=arctan(W/2X1−W/2∗tan(FOVh/2))
- 利用球机在1和2时刻的PTZ坐标,计算 P 1 P_1 P1到 P 2 P_2 P2的水平和垂直角度
{ α V : 1 − > 2 = T i l t 2 − T i l t 1 α H : 1 − > 2 = P a n 2 − P a n 1 \begin{cases} \alpha_{V:1->2} = Tilt_2-Tilt_1\\ \alpha_{H:1->2} = Pan_2-Pan_1\\ \end{cases} {αV:1−>2=Tilt2−Tilt1αH:1−>2=Pan2−Pan1
- 计算 P 2 P_2 P2到视野中心点,球机旋转的水平和垂直角度
{ α V : 2 − > o = α V : 1 − > o − α V : 1 − > 2 α H : 2 − > o = α H : 1 − > o − α H : 1 − > 2 \begin{cases} \alpha_{V:2->o}= \alpha_{V:1->o}-\alpha_{V:1->2}\\ \alpha_{H:2->o}= \alpha_{H:1->o}-\alpha_{H:1->2} \end{cases} {αV:2−>o=αV:1−>o−αV:1−>2αH:2−>o=αH:1−>o−αH:1−>2
- 计算 P 2 P_2 P2点的图像坐标
{ Y 2 = t a n ( α V : 2 − > o ) t a n ( F O V v / 2 ) ∗ H 2 + H 2 X 2 = t a n ( α H : 2 − > o ) t a n ( F O V h / 2 ) ∗ W 2 + W 2 \begin{cases} Y_2 = \frac{tan(\alpha_{V:2->o})}{tan(FOV_v/2)}*\frac{H}{2} + \frac{H}{2} \\ X_2 = \frac{tan(\alpha_{H:2->o})}{tan(FOV_h/2)}*\frac{W}{2} + \frac{W}{2} \end{cases} {Y2=tan(FOVv/2)tan(αV:2−>o)∗2H+2HX2=tan(FOVh/2)tan(αH:2−>o)∗2W+2W
实现
-
以海康球机
DS-2DC22041W-D3/W
为例 -
ONVIF协议作为标准通用的公有协议 ,可以实现摄像头PTZ控制
-
需要获取球机的PTZ和ONVIF的PTZ对应关系,参考《球机的PTZ和视场角与ONVIF的PTZ对应关系》
-
视场角标定,参考《视场角相关计算》
-
主要分为onvif控制、实时匹配和UI控制
- 隐私遮蔽实现主函数:privacy_shelter.py
class shelter:
def __init__(self, width, height, fov_h, fov_v, pos_onvif1, rect):
self.width = width
self.height = height
self.fov_h = fov_h
self.fov_v = fov_v
self.pos_sdk1 = self.ptz_to_sdk(pos_onvif1)
self.rect = rect # l,t,r,b
def ptz_to_sdk(self, pos_onvif):
p_ = min(180 * pos_onvif['pan'] + 180, 360)
t_ = 45 * pos_onvif['tilt'] + 45
z_ = 3 * pos_onvif['zoom'] + 1
return {'pan' :p_, 'tilt' :t_, 'zoom' :z_}
def ptz_to_onvif(self, pos_sdk):
p_ = (pos_sdk['pan'] - 180) / 180
t_ = (pos_sdk['tilt'] - 45) / 45
z_ = (pos_sdk['zoom'] - 1) / 3
return {'pan' :p_, 'tilt' :t_, 'zoom' :z_}
def locate(self, target):
x, y = target
# 水平方向
delt_x = np.rad2deg( np.arctan((x - self.width / 2) / (self.width / 2) * np.tan( np.deg2rad(self.fov_h /2))) )
# 垂直方向
delt_y = np.rad2deg( np.arctan((y - self.height / 2) / (self.height / 2) * np.tan( np.deg2rad(self.fov_v / 2))) )
return [delt_x, delt_y]
def match_point(self, point, pos_onvif2):
delt_x0, delt_y0 = self.locate(point)
pos_sdk2 = self.ptz_to_sdk(pos_onvif2)
delt_pos_sdk = [pos_sdk2['pan'] - self.pos_sdk1['pan'], pos_sdk2['tilt'] - self.pos_sdk1['tilt']]
delt_x2 = delt_x0 - delt_pos_sdk[0]
delt_y2 = delt_y0 + delt_pos_sdk[1]
x2 = np.tan(np.deg2rad(delt_x2)) / np.tan(np.deg2rad(self.fov_h / 2)) * self.width / 2 + self.width / 2
y2 = np.tan(np.deg2rad(delt_y2)) / np.tan(np.deg2rad(self.fov_v / 2)) * self.height / 2 + self.height / 2
return [x2, y2]
def match(self, pos_onvif2):
l2, t2 = self.match_point([self.rect[0], self.rect[1]], pos_onvif2)
r2, b2 = self.match_point([self.rect[2], self.rect[3]], pos_onvif2)
return [int(l2), int(t2), int(r2), int(b2)]