Openpose推断阶段原理

前言

之前出过一个关于openpose配置的博客,不过那个代码虽然写的很好,而且是官方的,但是分析起来很困难,然后再opencv相关博客中找到了比较清晰的实现,这里分析一波openpose的推断过程。

国际惯例,参考博客:

opencv官方文档,只有单人

大佬的实现,包括多人

解读

直接使用opencvdnn库调用openposecaffe模型,然后对输出进行后处理。重点是代表关节连接亲密度的亲和场的解析。

网络输出解析

推断阶段的模型结构(pose/coco)戳openpose官网,点这里跳转,可以使用netscope可视化。

最后一层的结构如下:

layer {name: "concat_stage7"type: "Concat"bottom: "Mconv7_stage6_L2"bottom: "Mconv7_stage6_L1"# top: "concat_stage7"top: "net_output"concat_param {axis: 1}

可以发现拼接了两个层:

  • Mconv7_stage6_L2

    layer {name: "Mconv7_stage6_L2"type: "Convolution"bottom: "Mconv6_stage6_L2"top: "Mconv7_stage6_L2"param {lr_mult: 4.0decay_mult: 1}param {lr_mult: 8.0decay_mult: 0}convolution_param {num_output: 19pad: 0kernel_size: 1weight_filler {type: "gaussian"std: 0.01}bias_filler {type: "constant"}}
    }
    
  • Mconv7_stage6_L1

    layer {name: "Mconv7_stage6_L1"type: "Convolution"bottom: "Mconv6_stage6_L1"top: "Mconv7_stage6_L1"param {lr_mult: 4.0decay_mult: 1}param {lr_mult: 8.0decay_mult: 0}convolution_param {num_output: 38pad: 0kernel_size: 1weight_filler {type: "gaussian"std: 0.01}bias_filler {type: "constant"}}
    }
    

可以发现,被拼接的两个层分别具有19和38个特征图。对照的网络结构图

在这里插入图片描述
两个stage,每个stage有两个branch:第一个branch输出191919个特征图,分别代表18个人体关键点和背景;第二个branch有38个特征图,代表文章所提出来亲和场(Part Affinity Fileds,PAF),代表关节与关节之前的联系。

代码里面对应关系:

keypointsMapping = ['Nose', 'Neck', 'R-Sho', 'R-Elb', 'R-Wr', 'L-Sho', 'L-Elb', 'L-Wr', 'R-Hip', 'R-Knee', 'R-Ank', 'L-Hip', 'L-Knee', 'L-Ank', 'R-Eye', 'L-Eye', 'R-Ear', 'L-Ear']POSE_PAIRS = [[1,2], [1,5], [2,3], [3,4], [5,6], [6,7],[1,8], [8,9], [9,10], [1,11], [11,12], [12,13],[1,0], [0,14], [14,16], [0,15], [15,17],[2,17], [5,16] ]# index of pafs correspoding to the POSE_PAIRS
# e.g for POSE_PAIR(1,2), the PAFs are located at indices (31,32) of output, Similarly, (1,5) -> (39,40) and so on.
mapIdx = [[31,32], [39,40], [33,34], [35,36], [41,42], [43,44], [19,20], [21,22], [23,24], [25,26], [27,28], [29,30], [47,48], [49,50], [53,54], [51,52], [55,56], [37,38], [45,46]]

POSE_PAIRS分别代表keypointsMapping里面同一根骨骼两端的两个人体关节(关键点)。

mapIdx:代表与POSE_PAIRS对应的亲和场特征图索引

【注】这里很容易出现疑问,为什么同一个关节,在向量场里面有不同的特征图索引呢?比如[1,2],[1,5]里面的关节1,在PAF特征图里面是索引31,39。这是因为一个关节可以被其它多个关节连接,而一个向量场PAF特征图只指向一个关节到另一个关节的链接,无法指向其它所有关节的链接。后面会可视化解释。

这里贴一下coco的人体人体关键点

在这里插入图片描述

调用模型

直接用opencvdnn.readNetFromCaffe来调用模型

protoFile = './models/pose/coco/pose_deploy_linevec.prototxt'
weightsFile = './models/pose/coco/pose_iter_440000.caffemodel'
net = cv2.dnn.readNetFromCaffe(protoFile,weightsFile)

然后输入一张图:

img = cv2.imread('./examples/media/COCO_val2014_000000000328.jpg')
frameWidth = img.shape[1]
frameHeight = img.shape[0]
inHeight = 368
inWidth = int((inHeight/frameHeight)*frameWidth)
inBlob = cv2.dnn.blobFromImage(img,1.0/255.0,(inWidth,inHeight),(0,0,0),swapRB=False,crop=False)
net.setInput(inBlob)
output=net.forward()
print(output.shape)#(1, 57, 46, 60)

可以发现输出的特征图个数和前面分析的相同。接下来随便可视化看看:

#可视化
plt.figure(figsize=[20,20])
plt.subplot(141)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.imshow(cv2.resize(output[0, 10, :, :], (frameWidth, frameHeight)), alpha=0.6)
plt.axis("off")
plt.subplot(142)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.imshow(cv2.resize(output[0, 18, :, :], (frameWidth, frameHeight)), alpha=0.6)
plt.axis("off")
plt.subplot(143)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.imshow(cv2.resize(output[0, 31, :, :], (frameWidth, frameHeight)), alpha=0.6)
plt.axis("off")
plt.subplot(144)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.imshow(cv2.resize(output[0, 39, :, :], (frameWidth, frameHeight)), alpha=0.6)
plt.axis("off")

在这里插入图片描述

从左到右,分别是:第10个关节点的特征图,背景特征图,(1,2)(1,2)(1,2)关节的关节111的亲和场PAF特征图,(1,5)(1,5)(1,5)关节的关节111的亲和场PAF特征图。

提取关键点

接下来就可以利用前面的18个特征图把肢体关键点提取出来。

对于某个关节的特征图,调用

def getKeypoints(probMap,threshold=0.1):mapSmooth = cv2.GaussianBlur(probMap,(3,3),0,0)mapMask = np.uint8(mapSmooth>threshold)keypoints = []_,contours,_ = cv2.findContours(mapMask,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)for cnt in contours:blobMask = np.zeros(mapMask.shape)blobMask = cv2.fillConvexPoly(blobMask,cnt,1)maskProbMap = mapSmooth*blobMask_,maxVal,_,maxLoc = cv2.minMaxLoc(maskProbMap)keypoints.append(maxLoc+(probMap[maxLoc[1],maxLoc[0]],))#位置和置信度return keypoints

就可以将当前关节的位置和对应的置信度提取出来。

提取所有的关节的位置和置信度,就相当于把每个关节的特征图遍历一遍:

nPoints = 18
def get_joint_kps(output):detected_keypoints = []keypoints_list = np.zeros((0,3))keypoints_id = 0threshold = 0.1for part in range(nPoints):probMap=output[0,part,:,:]probMap = cv2.resize(probMap,(img.shape[1],img.shape[0]))keypoints = getKeypoints(probMap,threshold)keypoints_with_id = []for i in range(len(keypoints)):keypoints_with_id.append(keypoints[i]+(keypoints_id,)) #所有人的18个关节位置、置信度、idkeypoints_list = np.vstack([keypoints_list,keypoints[i]])keypoints_id += 1detected_keypoints.append(keypoints_with_id)return detected_keypoints,keypoints_list

调用方法也很简单:

detected_keypoints,keypoints_list = get_joint_kps(output)

简单地看一下输出:

detected_keypoints
'''
[[(325, 165, 0.84138775, 0),(442, 143, 0.8589974, 1),(196, 133, 0.8166057, 2)],[(473, 176, 0.7320131, 3),(337, 165, 0.73004884, 4),(197, 133, 0.8598474, 5)],[(420, 176, 0.6951778, 6),(293, 154, 0.76514935, 7),(154, 133, 0.7135527, 8)],[(420, 261, 0.7520779, 9),(262, 218, 0.4267502, 10),(134, 197, 0.7333843, 11)],[(314, 251, 0.23509319, 12),(165, 228, 0.59333, 13),(453, 196, 0.6519662, 14)],[(388, 176, 0.62505144, 15),(518, 176, 0.6421095, 16),(240, 134, 0.6540677, 17)],[(549, 262, 0.73827094, 18),(389, 251, 0.71131617, 19),(240, 207, 0.6886268, 20)],[(495, 293, 0.62819993, 21),(357, 252, 0.7374373, 22),(207, 219, 0.560498, 23)],[(442, 282, 0.6578402, 24),(293, 252, 0.52459615, 25),(165, 228, 0.5512052, 26)],[(410, 283, 0.7377036, 27),(261, 272, 0.69384813, 28),(123, 239, 0.6885635, 29)],[(378, 431, 0.70034677, 30),(251, 411, 0.59873545, 31),(101, 356, 0.6479251, 32)],[(505, 283, 0.54467636, 33),(356, 271, 0.50983644, 34),(208, 229, 0.57463825, 35)],[(569, 314, 0.7413026, 36),(324, 293, 0.774911, 37),(228, 250, 0.7241578, 38)],[(538, 455, 0.58486414, 39),(282, 400, 0.46120968, 40),(207, 369, 0.56457037, 41)],[(314, 154, 0.8159541, 42),(433, 133, 0.72613055, 43),(186, 122, 0.80552864, 44)],[(335, 155, 0.8006719, 45),(453, 133, 0.8574599, 46),(206, 122, 0.80626, 47)],[(304, 133, 0.10505505, 48), (166, 111, 0.5242959, 49)],[(485, 144, 0.76806116, 50),(357, 143, 0.738666, 51),(218, 112, 0.73427236, 52)]]'''

可以看出来,这个数据是被分组的,总共18个组,分别代表18个关节,每组涵盖了当前图像所有人的这个关节的坐标和置信度,以及当前数据的编号,依次往下排,主要是为了索引keypoints_list里面的数据。这个keypoints_list里面是将所有的关键点的坐标和置信度不分组地塞到一起,所以维度是(53,3)(53,3)(53,3)

把关键点可视化瞅瞅呗:

#可视化关键点
img_show = img.copy()
for i in range(nPoints):for j in range(len(detected_keypoints[i])):cv2.circle(img_show,detected_keypoints[i][j][0:2],3,[0,255,0],-1,cv2.LINE_AA)
plt.figure(figsize=[8,8])
plt.imshow(img_show)
plt.axis('off')

在这里插入图片描述

区分关键点

上面提取了所有的关键点,但是没有计算哪个关键点属于哪个人,此时就需要根据亲和场计算各关键点之间的联系。比如第一个人大臂到三个人各自的小臂关键点的亲和场肯定不同,它只到属于自己的小臂关键点亲和场特征比较明显。知道这个道理,接下来分析一波。

下面就是找到与当前关键点最可能连接的肢体关键点是哪个。

根据mapIdx里面定义的亲和场索引:

先找到亲和场特征图

pafA = output[0,mapIdx[k][0],:,:] #第k组连接关节的第一个关节PAF
pafB = output[0,mapIdx[k][1],:,:] #第k组连接关节的第二个关节PAF
pafA = cv2.resize(pafA,(frameWidth,frameHeight))
pafB = cv2.resize(pafB,(frameWidth,frameHeight))

再找到对应的肢体关键点索引:

#找到这两个关节的位置
candA = detected_keypoints[POSE_PAIRS[k][0]] #找到第一个关节的位置(所有人)
candB = detected_keypoints[POSE_PAIRS[k][1]] #找到第二个关节的位置(所有人)

再看看亲和场怎么算的,先看论文的图

在这里插入图片描述

就是直接把两个关键点连起来,中间做一条线,计算亲和场上这条线上的值。

公式表达为
在这里插入图片描述
先计算dj2−dj1∥dj2−dj1∥2\frac{d_{j_2}-d_{j_1}}{\parallel d_{j_2}-d_{j_1} \parallel}_2dj2dj1dj2dj12

d_ij = np.subtract(candB[j][:2],candA[i][:2])
norm = np.linalg.norm(d_ij)
if(norm):d_ij = d_ij/norm #公式(10的d部分)
else:continue

画一条直线过去,得到PAF上每个点的值

n_interp_samples = 10
interp_coord = list(zip(np.linspace(candA[i][0],candB[j][0],num=n_interp_samples),np.linspace(candA[i][1],candB[j][1],num=n_interp_samples)))
paf_interp = []
for k in range(len(interp_coord)):paf_interp.append([pafA[int(round(interp_coord[k][1])),int(round(interp_coord[k][0]))],	pafB[int(round(interp_coord[k][1])),int(round(interp_coord[k][0]))]])

计算PAF得分和平均得分

paf_scores = np.dot(paf_interp,d_ij)
avg_paf_score = sum(paf_scores)/len(paf_scores)

使用PAF分数,进行阈值筛选

paf_score_th = 0.1
conf_th = 0.7
if(len(np.where(paf_scores>paf_score_th)[0])/n_interp_samples)>conf_th:if(avg_paf_score>maxScore):max_j = jmaxScore = avg_paf_scorefound = 1

找到关键点以后,就可以把当前关键点对儿的索引和得分保存

valid_pair = np.append(valid_pair,[[candA[i][3],candB[max_j][3],maxScore]],axis=0) #被连接的肢体的关键点索引

这里的整块代码写成函数如下,主要是额外加了一些得分不够和没有关键点的情况

def get_valid_pairs(output,detected_keypoints):valid_pairs = []invalid_pairs = []n_interp_samples = 10paf_score_th = 0.1conf_th = 0.7for k in range(len(mapIdx)):#两个可能连接的关节pafA = output[0,mapIdx[k][0],:,:] #第k组连接关节的第一个关节PAFpafB = output[0,mapIdx[k][1],:,:] #第k组连接关节的第二个关节PAFpafA = cv2.resize(pafA,(frameWidth,frameHeight))pafB = cv2.resize(pafB,(frameWidth,frameHeight))#找到这两个关节的位置candA = detected_keypoints[POSE_PAIRS[k][0]] #找到第一个关节的位置(所有人)candB = detected_keypoints[POSE_PAIRS[k][1]] #找到第二个关节的位置(所有人)nA = len(candA)nB = len(candB)#使用公式计算亲和场的得分if(nA!=0 and nB!=0): #如果有这两个关节valid_pair = np.zeros((0,3))for i in range(nA): #对于第一个关节的所有人遍历max_j = -1maxScore = -1found = 0for j in range(nB): #第二个关节的所有人遍历d_ij = np.subtract(candB[j][:2],candA[i][:2])norm = np.linalg.norm(d_ij)if(norm):d_ij = d_ij/norm #公式(10的d部分)else:continueinterp_coord = list(zip(np.linspace(candA[i][0],candB[j][0],num=n_interp_samples),np.linspace(candA[i][1],candB[j][1],num=n_interp_samples)))paf_interp = []for k in range(len(interp_coord)):paf_interp.append([pafA[int(round(interp_coord[k][1])),int(round(interp_coord[k][0]))],pafB[int(round(interp_coord[k][1])),int(round(interp_coord[k][0]))]])paf_scores = np.dot(paf_interp,d_ij)avg_paf_score = sum(paf_scores)/len(paf_scores)if(len(np.where(paf_scores>paf_score_th)[0])/n_interp_samples)>conf_th:if(avg_paf_score>maxScore):max_j = jmaxScore = avg_paf_scorefound = 1if found:valid_pair = np.append(valid_pair,[[candA[i][3],candB[max_j][3],maxScore]],axis=0) #被连接的肢体的关键点索引valid_pairs.append(valid_pair)else:#如果关节被遮挡等原因,导致不存在invalid_pairs.append(k)valid_pairs.append([])        return valid_pairs,invalid_pairs

稍微看一下这个函数的调用方法和返回的数据结构

#valid_pairs存储可成对的关节索引,所有人的每个关节成一组,比如3个人的第一个关节,组成一个3*3的矩阵
valid_pairs,invalid_pairs = get_valid_pairs(output,detected_keypoints)
'''
[array([[3.        , 6.        , 0.9164666 ],[4.        , 7.        , 0.85875524],[5.        , 8.        , 0.88577998]]), array([[ 3.        , 16.        ,  0.90284936],[ 4.        , 15.        ,  0.77933996],[ 5.        , 17.        ,  0.80140835]]), array([[ 6.        ,  9.        ,  0.89909419],[ 7.        , 10.        ,  0.52857684],[ 8.        , 11.        ,  0.71177599]]), array([[ 9.        , 14.        ,  0.8581396 ],[10.        , 12.        ,  0.38934133],[11.        , 13.        ,  0.8797749 ]]), array([[15.        , 19.        ,  0.81766873],[16.        , 18.        ,  0.78573793],[17.        , 20.        ,  0.67746843]]), array([[18.        , 21.        ,  0.63336505],[19.        , 22.        ,  0.88562933],[20.        , 23.        ,  0.7300858 ]]), array([[ 3.        , 24.        ,  0.7975674 ],[ 4.        , 25.        ,  0.53436182],[ 5.        , 26.        ,  0.79336061]]), array([[24.        , 27.        ,  0.80693887],[25.        , 28.        ,  0.59622135],[26.        , 29.        ,  0.80041958]]), array([[27.        , 30.        ,  0.78664207],[28.        , 31.        ,  0.73021965],[29.        , 32.        ,  0.6312245 ]]), array([[ 3.        , 33.        ,  0.90471435],[ 4.        , 34.        ,  0.75671906],[ 5.        , 35.        ,  0.75167511]]), array([[33.        , 36.        ,  0.68868005],[34.        , 37.        ,  0.86412876],[35.        , 38.        ,  0.71096365]]), array([[36.        , 39.        ,  0.82994086],[37.        , 40.        ,  0.86046369],[38.        , 41.        ,  0.9100325 ]]), array([[3.        , 1.        , 0.96472907],[4.        , 0.        , 0.97379622],[5.        , 2.        , 0.42410478]]), array([[ 0.        , 42.        ,  0.8114687 ],[ 1.        , 43.        ,  0.72544987],[ 2.        , 44.        ,  0.90721482]]), array([[44.        , 49.        ,  0.65025106]]), array([[ 0.        , 45.        ,  0.7345252 ],[ 1.        , 46.        ,  0.74511886],[ 2.        , 47.        ,  0.83590513]]), array([[45.        , 51.        ,  0.72804518],[46.        , 50.        ,  0.90572883],[47.        , 52.        ,  0.66244994]]), array([], shape=(0, 3), dtype=float64), array([], shape=(0, 3), dtype=float64)]
'''

同样是被分组了,总共有mapIdx对应19种连接方法,因为考虑到多人情况,所以每个连接方法又对应多条连接线。我们把这些边全连起来看看:

img_show = img.copy()
for pair in valid_pairs:for i in range(pair.shape[0]):conA = keypoints_list[int(pair[i][0])].astype(int)conB = keypoints_list[int(pair[i][1])].astype(int)cv2.line(img_show, (conA[0], conA[1]), (conB[0], conB[1]), colors[i], 3, cv2.LINE_AA)plt.imshow(img_show)
plt.axis('off')

在这里插入图片描述

看着基本没连错,第一个人的肩膀不会连到第二个人的胳膊肘,其它关键点一样。

分开存储

原理很简单,就是把有连接的边放到一个集合里面

# 根据获得的能被连接的关键点对,把坐标也对应好
def getPersonwiseKeyPoints(valid_pairs,invalid_pairs,keypoints_list):personwiseKeypoints = -1 * np.ones((0,19))for k in range(len(mapIdx)): #遍历有效的关节连接if(k not in invalid_pairs): #当前关节存在partAs = valid_pairs[k][:,0] #所有人第一个关节索引partBs = valid_pairs[k][:,1] #所有人第二个关节索引indexA,indexB = np.array(POSE_PAIRS[k]) #对应肢体的关键点索引for i in range(len(valid_pairs[k])): #当前关节有多少个数据点(人)found = 0person_idx = -1for j in range(len(personwiseKeypoints)):#遍历人                    if(personwiseKeypoints[j][indexA]==partAs[i]):person_idx = jfound=1breakif(found):personwiseKeypoints[person_idx][indexB] = partBs[i]personwiseKeypoints[person_idx][-1] += keypoints_list[partBs[i].astype(int),2]+valid_pairs[k][i][2]elif not found and k<17:row = -1*np.ones(19)row[indexA] = partAs[i]row[indexB] = partBs[i]row[-1] = sum(keypoints_list[valid_pairs[k][i,:2].astype(int),2]) + valid_pairs[k][i][2]personwiseKeypoints = np.vstack([personwiseKeypoints,row])return personwiseKeypoints

这块代码自己实现也行,反正能连接的边都在上一步知道了。这里只需要先执行后面的not found,构建几个personwiseKeypoints,然后再执行上面的found不断把上一个节点能连的下一个节点塞到对应位置。

输出:

personwiseKeypoints = getPersonwiseKeyPoints(valid_pairs,invalid_pairs,keypoints_list)
print(personwiseKeypoints)
'''
[[ 1.          3.          6.          9.         14.         16.18.         21.         24.         27.         30.         33.36.         39.         43.         46.         -1.         50.25.16836102][ 0.          4.          7.         10.         12.         15.19.         22.         25.         28.         31.         34.37.         40.         42.         45.         -1.         51.22.83992412][ 2.          5.          8.         11.         13.         17.20.         23.         26.         29.         32.         35.38.         41.         44.         47.         49.         52.25.00522498]]'''

从结果上来看是三个人,可视化看看

for i in range(17):for n in range(len(personwiseKeypoints)):index = personwiseKeypoints[n][np.array(POSE_PAIRS[i])]if -1 in index:continueB = np.int32(keypoints_list[index.astype(int), 0])A = np.int32(keypoints_list[index.astype(int), 1])cv2.line(img_show, (B[0], A[0]), (B[1], A[1]), colors[i], 3, cv2.LINE_AA)plt.figure(figsize=[15,15])
plt.imshow(img_show[:,:,[2,1,0]])

在这里插入图片描述

后记

本博客对应代码:
链接:https://pan.baidu.com/s/1ywFPXyTr-9vWbQnUIdjT2g
提取码:ajcl

后面有机会再解读一下openpose的网络搭建理论吧。

有兴趣的可以先看看:

《Convolutional Pose Machines》
《OpenPose: Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields》
Stacked Hourglass Networks for Human Pose Estimation

本文以同步到微信公众号中,代码也在公众号简介的GitHub中,有兴趣可以关注一波:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246582.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

换脸系列——眼鼻口替换

前言 想着整理一下换脸相关的技术方法&#xff0c;免得以后忘记了&#xff0c;最近脑袋越来越不好使了。应该会包含三个系列&#xff1a; 仅换眼口鼻&#xff1b;换整个面部&#xff1b;3D换脸 先看看2D换脸吧&#xff0c;网上已经有现成的教程了&#xff0c;这里拿过来整理一…

换脸系列——整脸替换

前言 前面介绍了仅替换五官的方法&#xff0c;这里介绍整张脸的方法。 国际惯例&#xff0c;参考博客&#xff1a; [图形算法]Delaunay三角剖分算法 维诺图&#xff08;Voronoi Diagram&#xff09;分析与实现 Delaunay Triangulation and Voronoi Diagram using OpenCV (…

3D人脸重建——PRNet网络输出的理解

前言 之前有款换脸软件不是叫ZAO么&#xff0c;分析了一下&#xff0c;它的实现原理绝对是3D人脸重建&#xff0c;而非deepfake方法&#xff0c;找了一篇3D重建的论文和源码看看。这里对源码中的部分函数做了自己的理解和改写。 国际惯例&#xff0c;参考博客&#xff1a; 什…

tensorflow官方posenet模型解析

前言 tensorflow官方有个姿态估计项目&#xff0c;这个输入和openpose还有点不一样&#xff0c;这里写个单人情况下的模型输出解析方案。 国际惯例&#xff0c;参考博客&#xff1a; 博客: 使用 TensorFlow.js 在浏览器端上实现实时人体姿势检测 tensorflow中posnet的IOS代…

tensorflow2安装时候的一个dll找不到的错误

电脑环境&#xff1a; vs2015python3.7.6&#xff0c;使用anaconda安装的CUDA 10.1cuDnn 7.6.5tensorflow2.1.0 错误内容 File "C:\Users\zb116\anaconda3\lib\imp.py", line 242, in load_modulereturn load_dynamic(name, filename, file)File "C:\Users\z…

PCA、SVD、ZCA白化理论与实现

简介 在UFLDL中介绍了主成分分析这一块的知识&#xff0c;而且当时学机器学习的时候&#xff0c;老师是将PCA和SVD联系起来将的&#xff0c;同时UFLDL也讲到了使用PCA做数据白化whitening处理&#xff0c;这个词经常在论文里面看到。 国际惯例&#xff0c;参考博客&#xff1…

OpenCV使用Tensorflow2-Keras模型

前言 最近工作上需要在C上快速集成Tensorflow/Keras训练好的模型&#xff0c;做算法验证。首先想到的就是opencv里面的dnn模块了&#xff0c;但是它需要的格式文件比较郁闷&#xff0c;是pb格式的模型&#xff0c;但是keras通常保存的是h5文件&#xff0c;查阅了很多资料&…

3D人脸表情驱动——基于eos库

前言 之前出过三篇换脸的博文&#xff0c;遇到一个问题是表情那一块不好处理&#xff0c;可行方法是直接基于2D人脸关键点做网格变形&#xff0c;强行将表情矫正到目标人脸&#xff0c;还有就是使用PRNet的思想&#xff0c;使用目标人脸的顶点模型配合源人脸的纹理&#xff0c…

3D姿态估计——ThreeDPose项目简单易用的模型解析

前言 之前写过tensorflow官方的posenet模型解析&#xff0c;用起来比较简单&#xff0c;但是缺点是只有2D关键点&#xff0c;本着易用性的原则&#xff0c;当然要再来个简单易用的3D姿态估计。偶然看见了ThreeDPose的项目&#xff0c;感觉很强大的&#xff0c;所以把模型扒下来…

简易的素描图片转换流程与实现

前言 之前经常在网上看到用PS实现真实图片到素描图片的转换&#xff0c;但是流程都大同小异&#xff0c;身为一只程序猿&#xff0c;必须来个一键转化额。 国际惯例&#xff0c;参考博客&#xff1a; Photoshop基础教程&#xff1a;混合模式原理篇 颜色减淡的原理讲解以及应…

一个简单好用的磨皮祛斑算法理论和python实现

前言 最近看了一个磨皮算法祛斑感觉效果不错&#xff0c;效果图看文末就行&#xff0c;个人觉得效果非常不错滴。 国际惯例&#xff0c;参考博客&#xff1a; 磨皮算法的源码&#xff1a;YUCIHighPassSkinSmoothing How To Smooth And Soften Skin With Photoshop 图像算法…

OpenVINO——配置与道路分割案例

前言 最近看到了一个深度学习库OpenVINO&#xff0c;专门用于Intel硬件上部署深度学习模型&#xff0c;其内置了非常非常多使用的预训练模型&#xff0c;比如道路分割、人脸提取、3D姿态估计等等。但是配置和调用有点小恶心&#xff0c;这里以道路分割为例&#xff0c;展示如何…

图像颜色迁移《color transfer between images》

前言 前段时间&#xff0c;在深度学习领域不是有个比较火的方向叫风格迁移的嘛&#xff0c;对于我这种不喜欢深度学习那种不稳定结果的人来说&#xff0c;还是想看看传统图像处理领域有什么类似的技术&#xff0c;发现了一个颜色迁移的算法&#xff0c;很久前的论文了。 国际…

ColorSpace颜色空间简介

前言 如果看过之前的介绍的图像颜色迁移《color transfer between images》和颜色协调模型Color Harmoniztion就会发现&#xff0c;大部分图像处理算法虽然输入输出是RGB像素值&#xff0c;但是中间进行算法处理时很少直接更改RGB值&#xff0c;而是转换到其它空间&#xff0c…

Ogre共享骨骼与两种骨骼驱动方法

前言 最近业务中用到Ogre做基于3D关键点虚拟角色骨骼驱动&#xff0c;但是遇到两个问题&#xff1a; 身体、头、眼睛、衣服等mesh的骨骼是分开的&#xff0c;但是骨骼结构都是一样的&#xff0c;需要设置共享骨骼驱动的时候可以直接修改骨骼旋转量&#xff0c;或者将旋转量存…

仿射变换和透视变换

前言 在前面做换脸的博客中提到了使用仿射变换和透视变换将两张不同的人脸基于关键点进行对齐&#xff0c;保证一张人脸贴到另一张人脸时&#xff0c;大小完全一致&#xff1b;所以有必要理解一下这两个概念的区别&#xff0c;由于以实用性为目的&#xff0c;所以所有的图像算…

obj格式解析

前言 最近处理一些网格渲染的时候&#xff0c;需要解析Obj文件&#xff0c;从Free3D上随便找了个免费的人体obj模型解析测试一波 国际惯例&#xff0c;参考博客&#xff1a; 本文所使用的从Free3D下载的模型 .obj文件格式与.mtl文件格式 详解3D中的obj文件格式 3D中OBJ文…

Flask服务部署与简单内网穿透

前言 最近学习部署的时候&#xff0c;想到深度学习里面通常用的部署方法是flask做服务端&#xff0c;然后使用nginx做负载均衡&#xff0c;貌似也能做内网穿透。不过我不太懂负载均衡&#xff0c;只想利用本地电脑搭建一个简单的服务器&#xff0c;实现外部调用API服务的功能。…

OpenCV学习——轮廓检测

前言 轮廓检测是传统视觉中非常常用的功能&#xff0c;这里简单记录一下opencv中的轮廓检测算法使用方法&#xff0c;至于理论&#xff0c;后续有机会再去细品。 国际惯例&#xff1a; OpenCV官方的轮廓检测教程python版 OpenCV中的二值化方法教程 OpenCV轮廓层级官方文档…

RBF神经网络理论与实现

前言 最近发现有挺多人喜欢径向基函数(Radial Basis Function,RBF)神经网络&#xff0c;其实它就是将RBF作为神经网络层间的一种连接方式而已。这里做一个简单的描述和找了个代码解读。 之前也写过一篇&#xff0c;不过排版不好看&#xff0c;可以戳这里跳转 国际惯例&#x…