要求:肤色检测
基于色度或其它彩色属性设计一个简单的肤色检测器。
- 用手机自拍一张人脸图像。
- 剪切照片或另外用画笔工具拾取那些可能是肤色的像素(人脸部位)。
- 对这些肤色像素计算彩色分布,如彩色直方图。(该属性的直方图)
- 对背景像素计算彩色分布。
- 用手机再自拍一张人脸图像,试着用这两个分布函数,在新拍图像中寻找肤色区域
学校cv课程的一个小作业。
思路分析:
首先确定所要选取的属性,比如题目中说的色度,或者其他自行挑选的属性,比如原文题目中的xy色度,就书里写的Yxy空间里的x,y色度,我选取了HSV空间里的H属性,即 色相。我个人感觉选取一个或多个属性都可,多个属性的话,无非就是对每个属性分别选取阈值,和单属性大同小异,都是卡范围。然后把不在范围里的像素值置零,就这样。
[230413] 突然发现说,这里为了早交作业只选了一个特征,如果要求是选取多个维度,比如我就用x,y色度,甚至我想用多个色彩空间的多个维度,来让识别效果更好怎么办呢?感觉问题就变成了一个类似二分类的想法,或者简单的异常检测,我把肤色“范围”之外的视为异常像素,就好了,重点在于所谓 “范围” 实际是多维度 “距离” 的计算,下面是简单的代码:
# 这里就 Yxy空间的 xy 两个维度写一下,原题目要求
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
def rgb2Yxy(img:np.ndarray):
img_XYZ = cv.cvtColor(img,code=cv.COLOR_RGB2XYZ)
img_xy = (img_XYZ/(np.sum(img_XYZ,axis=2)[:,:,None]))[:,:,:-1]
return img_xy
# 各种距离计算函数,选择一种都可,最常用就是欧式,即p=2的闵可夫斯基距离
# 但其实这里两个维度不是独立同分布,应该用马氏距离之类
# https://blog.csdn.net/zz2230633069/article/details/90374682 -> 别人写的距离函数
def distance_func(vec1, vec2, axis,p=2):
return np.linalg.norm(vec1 - vec2, ord=p,axis=axis)
# a 用来控制范围,1就是skin距离范围,值越大范围越大
def skin_filter(skin_img,target_img,a = 1.0):
skin_xy,target_xy = rgb2Yxy(skin_img),rgb2Yxy(target_img)
avg_xy = skin_xy.mean()
skin_distance = distance_func(skin_xy,avg_xy,axis=2,p=2)
range = skin_distance.max()-skin_distance.min()
mid =range/2+skin_distance.min()
skin_range = [mid-a*range/2,mid+a*range/2]
target_distance = distance_func(target_xy,avg_xy,axis=2,p=2)
final = target_img
final[(target_distance<skin_range[0]) | (target_distance>=skin_range[1])]=[0,0,0]
return final
skin = cv.imread('./skins/2_cropped_neck.jpg',flags=cv.IMREAD_COLOR)[:,:,::-1]
target = cv.imread('./selfies/11.jpg',cv.IMREAD_COLOR)[:,:,::-1]
filtered = skin_filter(skin,target,a=1.0)
plt.imshow(filtered,cmap='gray')
可以看到,在单维度效果不好的图片上效果会好一点:
以下是原文章。
完整代码在最后。
- 这里手动裁切一下肤色区域,比如面部和颈部。(图片来自网络,侵删,ITZY.Yeji)。
- 这里我们选择色相作为计算分布的属性,无他,简单有效耳。转换并画出色相分布直方图。
先观察一下三张图片色相值的取值范围:
大概都在0到180左右。
感觉还是能过滤掉相当一部分非肤色像素的。 - 这里就到了题目要求的第四步,但是我怀疑是我们老师对题目理解有偏差,大部分情况下背景应该是比较色相不均匀的,应该在背景比较单一的情况下用背景分割,这里摆一下原题目:
提到说,使用非肤色像素计算分布作为备选项。而且其实不看原题目要求的话,我们老师也没有很说清楚怎么去分割。原题目不仅明确要求用色度(chromaticity),也告诉我们可以用简单的均值方差方法,或均值偏移分割算法。不看原题目要求的话,我本人最简单的想法就是把肤色属性范围以外的像素排除掉。这里使用最简单的想法和均值方差blabla方法。 - 直接排除色相值所有不在肤色色度区间的所有像素。
使用采集的两个肤色色相区域分别过滤:
emmmmm效果还是可以的,颈部肤色色相区间覆盖更全面效果更好。(没覆盖腮红好像)
(PS:对不起美女,下次回归一直多多支持你)
5. 使用均值方差blabla:
就是卡一个色相范围,a值越大保留越多,可以根据效果选择最适当的a值。
然后我们大概封装一下去试一下别的图像效果怎么样。还是用hlz的自拍,她好美。
封装的是均值方差那个,多个参数玩。
封装函数:
测试代码:
效果:
只能说一般。
完整 notebook代码:
# %%
# created by Jinx7288 230317
import numpy as np
import matplotlib.pyplot as plt
import cv2 as cv
# %% 欣赏一下hlz美貌
selfie2 = cv.imread(cv.samples.findFile('./selfies/2.jpg'),cv.IMREAD_COLOR)
selfie2 = selfie2[:,:,::-1]
plt.axis('off')
plt.imshow(selfie2)
# %% 读入肤色取样
skin_face = cv.imread(cv.samples.findFile('./skins/2_cropped_face.jpg'),cv.IMREAD_COLOR)[:,:,::-1]
skin_neck = cv.imread(cv.samples.findFile('./skins/2_cropped_neck.jpg'),cv.IMREAD_COLOR)[:,:,::-1]
skin_list = [skin_face,skin_neck]
# %% 开始看错了,画了一下rgb直方图,不过可以看出用r去过滤也许也可以
plt.subplots_adjust(wspace=0.2,hspace=0.5)
for i,t in enumerate(['skin_face','skin_neck']):
plt.subplot(len(skin_list),2,2*i+1)
# plt.axis('off')
plt.title(t)
plt.imshow(skin_list[i])
plt.subplot(len(skin_list),2,2*i+2)
plt.title('histogram_'+t)
hists = [cv.calcHist([skin_list[i]],[j],None,[256],[0,256]) for j in range(3)]
for h,c in zip(hists,('r','g','b')):
plt.plot(h,color=c)
# %% 转换为hsv空间
skin_face_hsv = cv.cvtColor(skin_face,cv.COLOR_RGB2HSV)
skin_neck_hsv = cv.cvtColor(skin_neck,cv.COLOR_RGB2HSV)
selfie2_hsv = cv.cvtColor(selfie2,cv.COLOR_RGB2HSV)
hsv_list = [skin_face_hsv,skin_neck_hsv,selfie2_hsv]
for hsv in hsv_list:
print(hsv[:,:,0].min(),hsv[:,:,0].max())
# %% 画一下色相直方图
plt.subplots_adjust(wspace=0.2,hspace=0.5)
for i,t in enumerate(['skin_face','skin_neck','original']):
plt.subplot(len(hsv_list),2,2*i+1)
# plt.axis('off')
plt.title(t)
plt.imshow(hsv_list[i][:,:,0])
plt.subplot(len(hsv_list),2,2*i+2)
plt.title('hue_histogram_'+t)
plt.plot(cv.calcHist([hsv_list[i]],[0],None,[180],[0,180]))
# %% 在范围内保留,其他舍弃
selfie2_h = np.array(selfie2_hsv[:,:,0],dtype=int)
for i,e in enumerate([skin_face_hsv,skin_neck_hsv]):
hue_all_range = (e[:,:,0].min(),e[:,:,0].max())
mask = np.zeros(selfie2_h.shape)
mask[np.logical_and(selfie2_h>=hue_all_range[0],selfie2_h<hue_all_range[1])]=1
plt.subplot(2,2,2*i+1)
plt.imshow(mask,cmap='gray')
selfie2_hsv_temp = np.copy(selfie2_hsv)
for j in range(3):
new_channel = mask*np.array(selfie2_hsv[:,:,j],dtype=float)
selfie2_hsv_temp[:,:,j]= new_channel
plt.subplot(2,2,2*i+2)
new_selfie = cv.cvtColor(selfie2_hsv_temp,cv.COLOR_HSV2RGB)
plt.imshow(new_selfie)
# %% 在均值+-a*标准差范围内保留,其他舍弃
# 均值方差blabla,不过用的是标准差
a = 2
for i,e in enumerate([skin_face_hsv,skin_neck_hsv]):
h = np.array(e[:,:,0],dtype=float)
mean_h = h.mean()
std_h = np.std(h)
hue_mean_range = (mean_h-a*std_h,mean_h+a*std_h)
# print(hue_mean_range)
mask = np.zeros(selfie2_h.shape)
mask[np.logical_and(selfie2_h>=hue_mean_range[0],selfie2_h<hue_mean_range[1])]=1
plt.subplot(2,2,2*i+1)
plt.imshow(mask,cmap='gray')
selfie2_hsv_temp = np.copy(selfie2_hsv)
for j in range(3):
new_channel = mask*np.array(selfie2_hsv[:,:,j],dtype=float)
selfie2_hsv_temp[:,:,j]= new_channel
plt.subplot(2,2,2*i+2)
new_selfie = cv.cvtColor(selfie2_hsv_temp,cv.COLOR_HSV2RGB)
plt.imshow(new_selfie)
# %%
import numpy as np
import matplotlib.pyplot as plt
import cv2 as cv
def url2hsv(file:str):
pic = cv.imread(cv.samples.findFile(file),cv.IMREAD_COLOR)[:,:,::-1]
pic_hsv = cv.cvtColor(pic,cv.COLOR_RGB2HSV)
return pic_hsv,pic_hsv[:,:,0]
def skin_filter(skin_url,target_url,a=1.5):
skin_hsv,skin_h = url2hsv(skin_url)
target_hsv,target_h = url2hsv(target_url)
mean_h = skin_h.mean()
std_h = skin_h.std()
filter_range = (mean_h-a*std_h,mean_h+a*std_h)
mask = np.zeros(target_hsv[:,:,0].shape)
mask[np.logical_and(target_h>=filter_range[0],target_h<filter_range[1])]=1
for j in range(3):
new_channel = mask*np.array(target_hsv[:,:,j],dtype=float)
target_hsv[:,:,j]= new_channel
new_selfie = cv.cvtColor(target_hsv,cv.COLOR_HSV2RGB)
return mask,new_selfie
import os
selfies = os.listdir('./selfies/')
skin_neck_url = './skins/2_cropped_neck.jpg'
plt.subplots_adjust(wspace=0)
for i,ele in enumerate(selfies):
filtered = skin_filter(skin_url=skin_neck_url,target_url="./selfies/"+ele,a=3)
plt.subplot(len(selfies),3,i*3+1)
plt.axis('off')
plt.imshow(cv.imread(cv.samples.findFile("./selfies/"+ele),cv.IMREAD_COLOR)[:,:,::-1])
plt.subplot(len(selfies),3,i*3+2)
plt.axis('off')
plt.imshow(filtered[0],cmap='gray')
plt.subplot(len(selfies),3,i*3+3)
plt.axis('off')
plt.imshow(filtered[1])
# %%
skin_face_url = './skins/2_cropped_face.jpg'
for i,ele in enumerate(selfies):
print(ele)
filtered = skin_filter(skin_url=skin_face_url,target_url="./selfies/"+ele,a=3)
plt.subplot(len(selfies),3,i*3+1)
plt.axis('off')
plt.imshow(cv.imread(cv.samples.findFile("./selfies/"+ele),cv.IMREAD_COLOR)[:,:,::-1])
plt.subplot(len(selfies),3,i*3+2)
plt.axis('off')
plt.imshow(filtered[0],cmap='gray')
plt.subplot(len(selfies),3,i*3+3)
plt.axis('off')
plt.imshow(filtered[1])