介绍
多视角下匹配不同RGB图上的同一ID,并进行三角化重建。
编码ID规则:
编码规则为 一个中心点周围的 四个角点和另外四个边中心点 分成两组各自为循环数组,若一张图片上的编码即这两组循环数组都与另一张图上的相等,则认为这两个ID是同一个ID。
如下两幅示意图:
流程
代码
"""
author: daoboker
date: 2022.4.28
description: 编码规则为 四个角点和另外四个边中心点 分成两组各自为循环数组,\
若一张图片上的编码即这两组循环数组都与另一张图上的相等,则认为这两个ID是同一个ID
"""
# 色块的监测追踪以及打印中心坐标
import random
import copy
from collections import deque
import numpy as np
import cv2
from functools import reduce
import time
SUPER = 8
radius = 0
# 设定红色阈值,HSV空间
redLower1 = np.array([0, 43, 46])
redUpper1 = np.array([10, 255, 255])
redLower2 = np.array([156, 43, 46])
redUpper2 = np.array([180, 255, 255])
# 设定绿色阈值,HSV空间
greenLower = np.array([35, 43, 46])
greenUpper = np.array([77, 255, 255])
# 设定蓝色阈值,HSV空间
blueLower = np.array([100, 43, 46])
blueUpper = np.array([124, 255, 255])
# 设定紫色阈值,HSV空间
purpleLower = np.array([125, 43, 46])
purpleUpper = np.array([155, 255, 255])
mybuffer = 64 # 初始化追踪点的列表
pts = deque(maxlen=mybuffer)
image = cv2.imread('./picture/01.png', 1)
image2 = cv2.imread('./picture/04.png', 1)
# picture = cv2.resize(picture, (197, 108))
print('输入的图片大小为:', image.shape)
print('输入的图片大小为:', image2.shape)
# 判断两个循环数组是否相等
def equal(id1, id2):
if id1 == id2:
return True
cnt = 0
while (id1 != id2) and (cnt < len(id1)):
copy = [[] for i in range(len(id1))]
for i in range(len(id1)):
copy[i] = id1[(cnt+i+1)%len(id1)]
cnt += 1
if copy == id2:
return True
return False
# id生成前要按顺时针或者逆时针顺序生成
def id_order(list):
if list == [] :
return []
else:
list1 = copy.deepcopy(sorted(list, key=lambda k: k[0]))[0]
list2 = copy.deepcopy(sorted(list, key=lambda k: k[1]))[0]
list3 = copy.deepcopy(sorted(list, key=lambda k: k[0]))[-1]
list4 = copy.deepcopy(sorted(list, key=lambda k: k[1]))[-1]
dd = copy.deepcopy([list1, list2, list3, list4])
# print(tmp)
func = lambda x, y: x if y in x else x + [y]
quchong1 = copy.deepcopy(reduce(func, [[], ] + dd))
print('第一部分去除重复后的为:', quchong1)
rest = [i for i in list if i not in quchong1]
print('减去四个角后剩余的为:', rest)
if rest == []:
return dd
else:
list5 = copy.deepcopy(sorted(rest, key=lambda k: k[0]))[0]
list6 = copy.deepcopy(sorted(rest, key=lambda k: k[1]))[0]
list7 = copy.deepcopy(sorted(rest, key=lambda k: k[0]))[-1]
list8 = copy.deepcopy(sorted(rest, key=lambda k: k[1]))[-1]
ddd = [list1, list2, list3, list4, list5, list6, list7, list8]
print('tmp2:', ddd)
func = lambda x, y: x if y in x else x + [y]
rest2 = copy.deepcopy(reduce(func, [[], ] + ddd))
print('rest2', rest2)
last = []
for i in range(len(rest2)):
last.append(rest2[i][2])
print('last', last)
return last
# 获取每张图片的各个id,[[x,y,label...],....]
def get_id(image):
global radius
# 构建掩膜,求出轮廓
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 转到HSV空间
red_mask1 = cv2.inRange(hsv, redLower1, redUpper1) # 根据阈值构建掩膜
red_mask2 = cv2.inRange(hsv, redLower2, redUpper2)
red_mask = red_mask1 + red_mask2
red_mask = cv2.erode(red_mask, None, iterations=1) # 腐蚀操作,去除白噪点
red_mask = cv2.dilate(red_mask, None, iterations=2) # 膨胀操作,增大边缘体积。其实先腐蚀再膨胀的效果是开运算,去除噪点
red_contours, red_hierarchy = cv2.findContours(red_mask.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 轮廓检测,https://blog.csdn.net/Easen_Yu/article/details/89365497
green_mask = cv2.inRange(hsv, greenLower, greenUpper)
green_mask = cv2.erode(green_mask, None, iterations=1)
green_mask = cv2.dilate(green_mask, None, iterations=2)
green_contours, green_hierarchy = cv2.findContours(green_mask.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
blue_mask = cv2.inRange(hsv, blueLower, blueUpper)
blue_mask = cv2.erode(blue_mask, None, iterations=1)
blue_mask = cv2.dilate(blue_mask, None, iterations=2)
blue_contours, blue_hierarchy = cv2.findContours(blue_mask.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
purple_mask = cv2.inRange(hsv, purpleLower, purpleUpper)
purple_mask = cv2.erode(purple_mask, None, iterations=1)
purple_mask = cv2.dilate(purple_mask, None, iterations=2)
purple_contours, purple_hierarchy = cv2.findContours(purple_mask.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# center = None # 初始化圆形轮廓质心
# print(len(redContours))
# 创建颜色信息字典,以0123来代表四种颜色,BGR格式
color = {'0': (0, 0, 255), '1': (0, 255, 0), '2': (255, 0, 0), '3': (128, 0, 128)}
# 构造标签列表
label_list = []
# 当为不同颜色时,即label为0123时,构造列表[[x,y,label],...]
for i in range(len(red_contours)):
((center_x, center_y), radius) = cv2.minEnclosingCircle(red_contours[i])
# 计算轮廓的矩
M = cv2.moments(red_contours[i])
if radius < 8:
# # 计算质心
# cv2.circle(image, (int(center_x), int(center_y)), int(radius), (255, 255, 255), 2)
label_list.append([int(center_x), int(center_y), 0])
# cv2.circle(image, center, 5, (0, 0, 255), -1)
# print('红色色块的中心坐标', (int(center_x), int(center_y)))
for i in range(len(green_contours)):
((center_x, center_y), radius) = cv2.minEnclosingCircle(green_contours[i])
# 计算轮廓的矩
M = cv2.moments(green_contours[i])
if radius < 8:
label_list.append([int(center_x), int(center_y), 1])
for i in range(len(blue_contours)):
((center_x, center_y), radius) = cv2.minEnclosingCircle(blue_contours[i])
# 计算轮廓的矩
M = cv2.moments(blue_contours[i])
if radius < 8:
label_list.append([int(center_x), int(center_y), 2])
for i in range(len(purple_contours)):
((center_x, center_y), radius) = cv2.minEnclosingCircle(purple_contours[i])
# 计算轮廓的矩
M = cv2.moments(purple_contours[i])
if radius < 8:
label_list.append([int(center_x), int(center_y), 3])
'''
暴力遍历匹配算法
label_list --> [[x,y,1个label],...]
id --> [[x,y,9个label],...]
'''
id = copy.deepcopy(label_list)
for i in range(len(label_list)):
tmp = []
for j in range(len(label_list)):
if ((label_list[i][0] - label_list[j][0])**2) + ((label_list[i][1] - label_list[j][1])**2) < (27)**2 and (i != j):
tmp.append(label_list[j])
# print('label_list[j]', label_list[j])
print('原始数据:', tmp)
id[i].extend(id_order(tmp))
print('id[{}]'.format(i), id[i])
# print('label_list[i]', label_list[i])
print('--------------------------')
return id
if __name__ == '__main__':
start_time = time.time()
id1 = copy.deepcopy(get_id(image))
id2 = copy.deepcopy(get_id(image2))
print(len(id1))
# Create a new output image that concatenates the two images together
# (a.k.a) a montage
rows1 = image.shape[0]
cols1 = image.shape[1]
rows2 = image2.shape[0]
cols2 = image2.shape[1]
out = np.zeros((max([rows1, rows2]), cols1 + cols2, 3), dtype='uint8')
# Place the first image to the left
out[:rows1, :cols1] = image
# Place the next image to the right of it
out[:rows2, cols1:] = image2
# 2-判断id列表里相同的两个id,比较的是列表的第三个数开始到最后的数即为颜色标签
for i in range(len(id1)):
for j in range(len(id2)):
if equal(id1[i][3:7], id2[j][3:7]) and equal(id1[i][7:], id2[j][7:]) and (id1[i][2] == id2[j][2]): # 从第二位label即周围一圈的label开始比较
cv2.circle(image, (id1[i][0], id1[i][1]), int(radius), (255, 0, 255), 2)
cv2.circle(image2, (id2[j][0], id2[j][1]), int(radius), (255, 0, 255), 2)
# if random.randint(1, 8) == 3:
# cv2.line(out, (id1[i][0], id1[i][1]), (id2[j][0]+cols1, id2[j][1]), (0,255,255), 1)
print([id1[i], id2[j]])
end_time = time.time()
print('程序所花的时间为:', end_time - start_time)
cv2.namedWindow('image1', 0)
cv2.resizeWindow('image1', 259, 561) # 自己设定窗口图片的大小
cv2.namedWindow('image2', 0)
cv2.resizeWindow('image2', 259, 561) # 自己设定窗口图片的大小
cv2.imshow('image1', image)
cv2.imshow('image2', image2)
cv2.namedWindow('out', 0)
cv2.resizeWindow('out', 259, 561) # 自己设定窗口图片的大小
cv2.imshow('out', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
其他编码规则
四个格子+三种颜色
Trackable Surfaces
此种编码方式的ID数为18,
计算过程为。由于此四个格子组成的标记需要带方向性,所以需要减去自身中心对称的情况。
此种编码方式
①标记本身形状较大的情况下容易被检测;
②较少的格子和颜色,可考虑加颜色数,例如加一种颜色ID数变为57种;
③各标记之间步长为两个田字格中心之间的距离,不够紧凑;
圆点排列+四种颜色
Garment-based motion capture (GaMoCap): high-density capture of human shape in motion
此种编码方式的ID数为32640,
计算过程为。由于此9个圆组成的标记也需要带方向性,所以减去自身旋转对称的情况。
此种编码方式:
①因为格子和颜色相较于第一种都有增加,所以ID数有了相当大的增加;
②各标记之间步长就是圆点圆心之间的距离,比较紧凑;
总结:
- 第一种编码方式可能更加倾向于角点的检测,若是采用滤出颜色得到颜色中心坐标的方法,也很难得出一种标记ID规则和ID坐标的确定方法。
- 而第二种编码方式可以根据滤出颜色的坐标进而得到周围8个圆的颜色信息,从而得到整个标记的ID信息,并且可以以中心圆的圆心作为标记坐标。
后续工作
需要解决自遮挡产生折叠编码,互遮挡看不到编码的问题,还有速度上的问题;
想着可以利用已有的模型去拟合被遮挡未能重建的部分;
带标记的手部光学动捕的遮挡问题
检测中存在两类遮挡(1)待检测的目标之间相互遮挡;(2)待检测的目标被干扰物体遮挡。
解决方法:
- 增加相机数量,用已有模型去拟合
Capturing Detailed Deformations of Moving Human Bodies
关于遮挡,由于点的3D信息是通过三角化实现的,因此一个标记必须被多个相机直接看到。在实践中对于非常复杂的运动,需要增加了要部署的相机数量以增加看到标记的机会。附加运动学模型可以减少这种问题的影响,因为被遮挡的标记位置可以通过其他标记的位置来预测。
即使在大的遮挡情况下,只要整个两个字母的代码可见,作者的方法仍然可以获得正确标记的角落。并且正确地拒绝了腹部区域的皱纹四边形,因为读取代码将是困难的或者不可能的。
由于相机数量有限,3D重建的(三角)点将不可避免地错过观测。作者首先拟合一个已有的模型,使用这个精致的身体模型来插入静止姿势中缺失的角,从而得到没有任何孔洞的最终网格。
- 通过其他帧的无遮挡部分来插补空洞
Capturing and Animating Occluded Cloth
在获取过程中,遮挡不可避免地会在重建的网格中产生孔洞。作者用数据驱动的方法来填充孔洞:即使用来自其他帧的无遮挡网格来自动插补空洞。
例如在下图情况中,左腿的大腿内侧区域很难观察到,因为右腿遮挡了它的视线。之后必须填充包含错误视图、单视角或双视角中所看到的区域。