基于色度或其他彩色属性设计一个简单的肤色检测器

要求:肤色检测
基于色度或其它彩色属性设计一个简单的肤色检测器。

  1. 用手机自拍一张人脸图像。
  2. 剪切照片或另外用画笔工具拾取那些可能是肤色的像素(人脸部位)。
  3. 对这些肤色像素计算彩色分布,如彩色直方图。(该属性的直方图
  4. 对背景像素计算彩色分布。
  5. 用手机再自拍一张人脸图像,试着用这两个分布函数,在新拍图像中寻找肤色区域

学校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')

可以看到,在单维度效果不好的图片上效果会好一点:
在这里插入图片描述

以下是原文章。
完整代码在最后

  1. 这里手动裁切一下肤色区域,比如面部和颈部。(图片来自网络,侵删,ITZY.Yeji)。
    resources
  2. 这里我们选择色相作为计算分布的属性,无他,简单有效耳。转换并画出色相分布直方图。
    先观察一下三张图片色相值的取值范围:
    在这里插入图片描述
    大概都在0到180左右。
    在这里插入图片描述
    感觉还是能过滤掉相当一部分非肤色像素的。
  3. 这里就到了题目要求的第四步,但是我怀疑是我们老师对题目理解有偏差,大部分情况下背景应该是比较色相不均匀的,应该在背景比较单一的情况下用背景分割,这里摆一下原题目:
    在这里插入图片描述
    提到说,使用非肤色像素计算分布作为备选项。而且其实不看原题目要求的话,我们老师也没有很说清楚怎么去分割。原题目不仅明确要求用色度(chromaticity),也告诉我们可以用简单的均值方差方法,或均值偏移分割算法。不看原题目要求的话,我本人最简单的想法就是把肤色属性范围以外的像素排除掉。这里使用最简单的想法和均值方差blabla方法。
  4. 直接排除色相值所有不在肤色色度区间的所有像素。
    使用采集的两个肤色色相区域分别过滤:
    在这里插入图片描述

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])




  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值