前不久,身边一朋友要送女朋友生日礼物,送完就被女朋友吐槽了一顿。


什么???今年又送了死亡芭比粉???情人节也是???
 
如果说彩妆中最受女生欢迎的是哪一个,相信所有人都会脱口而出:口红。尤其是有女朋友的男孩子更能明白,有了口红就不要男票的心痛吧。所以,你们给女朋友买口红,就选择死亡芭比粉是吗?

对于广大“钢铁直男”的程序员来说,送什么礼物给女朋友一直是个世纪难题

其实哄女朋友开心最深的套路就是花式送口红,就问谁抵挡得住啊啊啊啊…

“没有什么问题是一支口红解决不了的,如果有,那就两支。”于是,直男们纷纷开始各种买口红、送口红……

毕竟李佳琦一句"OMG买它”,女朋友披头散发抢购,钱包就空了一半。

但是,口红色号千千万,选对了牌子才成功了一半。

快乐橙、伤心紫,姨妈红,鸡屎绿…直男眼里没什么区别的颜色,在女生眼里各种色调、质地细微的区别都能分析一清二楚。

那么,对于直男来说,怎么才能搞清楚如此多的口红色号呢?

我耗费一毫米发际线,琢磨了一下,做出了一个口红色号识别器,希望能帮大家在关键时刻把深刻的革命友谊再升华一下。

先来看看效果。让我们假设,小姐姐发来了一张美妆博主的美照,并暗示你,“人家也喜欢这个颜色。”

这个时候,用我们的口红色号识别器,就能定位嘴唇,并迅速给出它的颜色隶属哪家品牌的哪个色号。

OMG!简直比李佳琦还准确!

好啦,废话不多说,马上开始教学时间!

要想识别口红色号,先得让机器知道到底都有哪些颜色。

听柜姐介绍,红色系有:“草莓红、铁锈红、枫叶红…”,其他还有“豆沙色、吃土色、番茄色…

世界观还未建立完全就要开始土崩瓦解,这看着有区别吗?“豆沙色最为百搭,橘调的番茄色比较显白…”眼前的黑不是黑,你说的红是什么红?

还好,在万能的 Github 上找到了一个宝藏数据库“口红颜色可视化”,这个数据库堪比口红的色号宇宙,不仅囊括了当前最主流品牌的各种系列色号,还很良心的在色盘上排列了出来。

这个数据集是一个嵌套的字典数据结构,存为 json 串的形式,里面记录了每个口红品牌系列下不同口红色号的颜色 id、名称、和 16 进制颜色值。

直!男!救!星!有木有!

不过看着这密密麻麻的颜色,真心佩服各大口红品牌的文案高手,是怎么样区别每一个看不出区别的颜色,并且还要分别取名字的。

傻傻分不清的我对 5 个品牌的不同系列做了一下统计和色号录入,于是,剩下的就交给计算机啦。

先用番茄做个实验?

既然有了如此完备的色号数据库,那么文摘菌就有了一个讨巧的方法:要想找到合适的色号,可以直接截取颜色,然后在数据库中进行比对。

这个方法非常好操作,在上唇色之前,我们不如先拿别的红色物品来练手。

比如,这里有一只番茄图片,你看这个番茄它又大又圆:

在其中截取了成色均匀、无高亮的矩形图片:

提取这张纯色图片的 RGB 值在技术上是可行的,getcolor.py 代码如下:

import colorsys
import PIL.Image as Image def get_dominant_color(image): max_score = 0.0001 dominant_color = None for count,(r,g,b) in image.getcolors(image.size[0]*image.size[1]): # 转为HSV标准 saturation = colorsys.rgb_to_hsv(r/255.0, g/255.0, b/255.0)[1] y = min(abs(r*2104+g*4130+b*802+4096+131072)>>13,235) y = (y-16.0)/(235-16) #忽略高亮色 if y > 0.9: continue score = (saturation+0.1)*count if score > max_score: max_score = score dominant_color = (r,g,b) return dominant_color

为了减少误差,需要裁剪多个不同位置的图片,保存在本地的一个文件夹中,读取文件,提取颜色,求平均值,得到的番茄最终的 RGB 颜色,代码如下:

import os
import getcolor
from os.path import join as pjoin
from scipy import misc def load_color(color_dir,list):  count = 0 for dir in os.listdir(color_dir):   img_dir = pjoin(color_dir, dir)   image = getcolor.Image.open(img_dir) image = image.convert('RGB') get=getcolor.get_dominant_color(image) list.append(get) count = count+1 #print(person_dir) #print(count) return count def Mean_color(count,list): Mean_R=Mean_G=Mean_B=0 for i in range(count): tuple=list[i] Mean_R+=tuple[0] Mean_G+=tuple[1] Mean_B+=tuple[2] MeanC=((int)(Mean_R/count),(int)(Mean_G/count),(int)(Mean_B/count)) return Me

番茄的颜色提取到了,那么和什么做比对呢?

当然是口红的数据,文摘菌这儿用到了 5 个品牌,分别是圣罗兰、香奈儿可可小姐、迪奥、美宝莲、纪梵希,共 17 个系列,271 个口红色号。

数据集是一个嵌套的字典数据结构,存为 json 串的形式,里面记录了每个口红品牌系列下不同口红色号的颜色 id、名称、和 16 进制颜色值。

lipstick.json部分数据集展示如下:

{"brands":[{"name":"圣罗兰","series":
[{"name":"莹亮纯魅唇膏","lipsticks":
[{"color":"#D62352","id":"49","name":"撩骚"},
{"color":"#DC4B41","id":"14","name":"一见倾心"},
{"color":"#B22146","id":"05","name":"浮生若梦"},

数据集中存储的 RGB 颜色是 16 进制的字符串形式,需要将其转换成 RGB 值,比较两个颜色相近与否。

实际上是比较 RGB 三个分量维度上的误差,最小的口红输出对应的品牌、系列、色号和 id。

代码如下:

import json
import getcolor
import numpy as np
import lipcolor #filename = 'temp.txt'
##write the temp data to file##
def WtoFile(filename,RGB_temp): num=len(RGB_temp) with open(filename,'w') as f:  for i in range(num): s = str(RGB_temp[i]).replace('[','').replace(']','') f.write(s) f.write("\n") #operate the data #
##save the brand&series&color id&color name to sum_list##
##covert the color #D62352 to RGB_array##
##caculate the RGB difference to RGB_temp and write the value to file##
def data_operate(): with open('lipstick.json', 'r', encoding='utf-8') as f: ret_dic = json.load(f) #print(ret_dic['brands']) #print(type(ret_dic)) # <class 'dict'> #print(ret_dic['brands'][0]['name'])  b_num=len(ret_dic['brands']) #print(b_num)#brands number s_list=[] #series brands# for i in range(len(ret_dic['brands'])): s_num=len(ret_dic['brands'][i]['series']) s_list.append(s_num) #print("{0} has {1} series".format((ret_dic['brands'][i]['name']),(s_list[i]))) #the lipstick color of every brands every series# #the first loop calculate the total color numbers sum=0 for b1 in range(b_num): for s1 in range(s_list[b1]): brand_name=ret_dic['brands'][b1]['name'] lip_name=ret_dic['brands'][b1]['series'][s1]['name'] color_num=len(ret_dic['brands'][b1]['series'][s1]['lipsticks']) sum+=color_num#calculate the total color numbers #the second loop save the message to a list# sum_list=np.zeros((sum,4), dtype=(str,8)) value_array=np.zeros((sum,6), dtype=int) i=0     for b2 in range(b_num): for s2 in range(s_list[b2]): brand_name=ret_dic['brands'][b2]['name'] #print(type(brand_name)) lip_name=ret_dic['brands'][b2]['series'][s2]['name'] color_num=len(ret_dic['brands'][b2]['series'][s2]['lipsticks']) for c in range(color_num): color_value=ret_dic['brands'][b2]['series'][s2]['lipsticks'][c]['color'] color_name=ret_dic['brands'][b2]['series'][s2]['lipsticks'][c]['name'] color_id=ret_dic['brands'][b2]['series'][s2]['lipsticks'][c]['id'] #print("{0} series {1} has {2} colors,color {3}:{4}".format(brand_name,lip_name,color_num,c+1,color_name)) sum_list[i][0]=brand_name sum_list[i][1]=lip_name sum_list[i][2]=color_id sum_list[i][3]=color_name #value_array[i]=value_array[i][1] #convert "#D62352" to [13  6  2  3  5  2]# for l in range(6): temp=color_value[l+1] if(temp>='A'and temp<='F'): temp1=ord(temp)-ord('A')+10 else: temp1=ord(temp)-ord('0') value_array[i][l]=temp1 i+=1 #the third loop covert value_array to RGB_array# RGB_array=np.zeros((sum,3), dtype=int) for i in range(sum): RGB_array[i][0]=value_array[i][0]*16+value_array[i][1] RGB_array[i][1]=value_array[i][2]*16+value_array[i][3] RGB_array[i][2]=value_array[i][4]*16+value_array[i][5] #calculate the similar and save to RGB_temp #RGB_temp=np.zeros((sum,1), dtype=int) RGB_temp=np.zeros((sum,1), dtype=float) for i in range(sum): R=RGB_array[i][0] G=RGB_array[i][1] B=RGB_array[i][2] RGB_temp[i]=abs(get[0]-R)+abs(get[1]*3/4-G)+abs(get[2]-B) RGB_temp.tolist();#covert array to list #print(RGB_temp) filename="temp.txt" WtoFile(filename,RGB_temp) #sort the RGB_temp# result=sorted(range(len(RGB_temp)), key=lambda k: RGB_temp[k]) #print(result) #output the three max prob of the lipsticks# print("The first three possible lipstick brand and color id&name are as follows:") for i in range(3): idex=result[i] print(sum_list[idex]) print("The first three possible lipstick brand RGB value are as follows:") for i in range(3): idex=result[i] R=RGB_array[idex][0] G=RGB_array[idex][1] B=RGB_array[idex][2] tuple=(R,G,B) print(tuple) if __name__ == '__main__': #image = getcolor.Image.open(inputpath) #image = image.convert('RGB') #get=getcolor.get_dominant_color(image)#tuple #get=(231, 213, 211) list=[] color_dir="output" count=lipcolor.load_color(color_dir,list) get=lipcolor.Mean_color(count,list) print("the extracted RGB value of the color is {0}".format(get)) #operate the data# data_operat

输出最有可能吻合番茄颜色的前三个口红的信息,然后在 Spyder 中的运行结果:

可以看到最有可能的三个口红品牌色号的 RGB 值与番茄的 RGB 值是非常接近的。

提取到的番茄颜色:

‘迪奥’ ‘烈艳蓝金唇膏’ ‘080’ '微笑正红’的颜色:

‘圣罗兰’ ‘纯口红’ ‘56’ '橙红织锦’的颜色:

‘纪梵希’ ‘高定香榭天鹅绒唇’ ‘325’ '圣水红’的颜色:

我已经眼花缭乱,三个颜色……有区别吗?!以后不如准备统一叫它们,番茄色!

不过,这也正说明了,刚刚的提取&对比方法可行!

既然可以识别番茄的颜色,那么,可以识别人像中的口红色号吗?

进入正题!人像口红色号识别

接下来,我们需要做的是输入一张人像图片,可以自动识别其中的嘴唇区域,并提取出嘴唇区域中的一部分做为颜色提取的源图像。

这里就要用到 CV 的人脸识别了,还好 Dlib 库又帮助我们减轻一大部分的工作量。

Dlib 中有自带的 68 个人脸的识别器,可以得到人脸部位包括眉毛、眼睛、鼻梁、面部轮廓和嘴唇区域的具体点的位置,到这儿,我以为很轻松就可以截到嘴唇区域了,结果有点尴尬…

我们首先找到了一张小姐姐的照片:

截取到的嘴唇区域如下:

很明显的看到上下嘴唇黑色的区域也截取到了,这对后续的提色有影响,所以不得不回到最初的 68 个检测点来思考人生。

标记的 68 个人脸检测点如上图所示,而嘴唇部位是从第 49 个标记点开始的(数组的话,下标是 48)。

为了尽可能的截取到均匀成色的嘴唇片段,刚开始是想从第 50 个标记点对角线截取到第 56 个标记点,而这不可避免的会截取到上下嘴唇之间的缝隙,这儿的阴影也会影响后续的颜色提取准确度。

考虑到下嘴唇比上嘴唇宽,所以截取到下嘴唇中间的两个小正方形区域:

人脸识别和截取嘴唇区域的代码如下:

import numpy as np
import cv2
import dlib
from PIL import Image def crop(source,pos): x1=pos[2][0] y1=pos[2][1] x2=pos[1][0] y2=pos[1][1] d=abs(x2-x1) region = source[(int)(y1-d*0.75):y2,x1:x2] # save the image cv2.imwrite("output/Mouth1.jpg", region) x1=pos[1][0] y1=pos[1][1] x2=pos[0][0] y2=pos[0][1] d=abs(x1-x2) region = source[y1-d:y2,x1:x2] # save the image cv2.imwrite("output/Mouth2.jpg", region) def detect_mouth(img,pos): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray) detector = dlib.get_frontal_face_detector() #use the predictor  predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat') dets = detector(img, 1)    print("Number of faces detected: {}".format(len(dets))) for a in dets:     cv2.rectangle(img,(a.left(),a.top()),(a.right(),a.bottom()),(255,0,0)) #point_list=[]#save the mouth point to point_list[]# #Extract 68 feature points of the face and crop the lip image# for index, face in enumerate(dets): print('face {}; left {}; top {}; right {}; bottom {}'.format(index, face.left(), face.top(), face.right(), face.bottom())) shape = predictor(gray, face) for i, pt in enumerate(shape.parts()): #print('Part {}: {}'.format(i, pt)) #print(i) pt_pos = (pt.x, pt.y) if i>=48 and i<=67: cv2.circle(img, pt_pos, 2, (255, 0, 0), 1) if i>=56 and i<=58: #print(pt_pos) pos[i-56][0]=pt.x pos[i-56][1]=pt.y #cv2.circle(img, pt_pos, 2, (255, 0, 0), 1) return img if __name__ == "__main__":  img = cv2.imread("test3.png") #copy the input image for the later crop# img_clone = np.copy(img) cv2.imwrite("input/source.jpg",img_clone) #save the lip position to pos array# pos=np.zeros((3,2), dtype=int) result=detect_mouth(img,pos) cv2.imwrite("input/source2.jpg",result) #crop the lip areas# source = cv2.imread("input/source.jpg") crop(source,pos) # show the result cv2.imshow('FaceDetect',result) cv2.waitKey(0)  cv2.destroyAllWindow

既然已经截取到嘴唇的小矩形图像了,接下来的工作就和前面一样了,在数据库中对比每个 RGB 值输出最小误差对应的口红信息,而这儿也有难到我。

单纯的比对 RGB 分量对口红色号来说并不适用,有可能每个分量相差很小,而叠加起来的颜色和提取到的颜色并不相似,在颜色的比对上需要手动调参。

几经波折,最后输出的结果还是可以接受的,上图人像中涂的口红色号,感兴趣的读者可以查下正好是下面输出排名第一的口红信息。

误差分析


对于我们测试的图片信息,标记了嘴唇区域的特征点,我们提取到的 RGB 值(156,59,103)颜色如下所示:

可以看到和图片的颜色已经十分接近了,而数据集合 lipstick.json 中这种口红存储的 16 进制颜色值为 #842C71,对应的颜色如下:

明显看到数据集存储的颜色和实际照片的颜色是有些许误差的,而在本文算法实现过程中,又不可避免的有以下误差:

  • 嘴唇区域截取不可避免会截取到皮肤中的一部分颜色,虽然算法已经将那种可能降到最低。
  • 颜色提取上,虽然截取多个嘴唇图片求平均值,但是本身的提取算法还是和实际值稍有偏差。
  • RGB 颜色相似度比对的算法也不够精确。
  • 最最重要的是,照片必须是原图,而且光线要自然,加了滤镜的图是怎么也不可能识别出来的。

以上种种,使得让计算机快速高效地识别不同的口红色号还是有困难的,原来计算机有时候也会很直男。

实时人像口红色号预测

看到这儿,可能很多读者朋友想实时地试一下能不能让计算机判断自己的口红色号,这对于 OpenCV 这一强大的图形操作库来说,不是什么问题。

它可以打开你的摄像头,读取每一帧的图片,结合前文提到的人脸识别代码,可以实时地截取到嘴唇区域的图片,然后交给计算机预测,从此再也不怕女朋友的灵魂拷问!


最后,附上打开摄像头的代码,快叫女朋友过来试下吧!

#coding=utf8
import cv2
import time
print('Press Esc to exit')
imgWindow = cv2.namedWindow('FaceDetect', cv2.WINDOW_NORMAL)
import sys
import os
import dlib
import glob
import numpy
from skimage import io
def detect_face(): capInput = cv2.VideoCapture(0) #nextCaptureTime = time.time() faces = [] feas = []  if not capInput.isOpened(): print('Capture failed because of camera') while 1: ret, img = capInput.read() gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray) time=0 eTime = time.time() + 0.1 detector = dlib.get_frontal_face_detector()  predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat') dets = detector(gray, 1)   print("Number of faces detected: {}".format(len(dets))) for a in dets:     cv2.rectangle(img,(a.left(),a.top()),(a.right(),a.bottom()),(255,0,0)) for index, face in enumerate(dets): print('face {}; left {}; top {}; right {}; bottom {}'.format(index, face.left(), face.top(), face.right(), face.bottom())) shape = predictor(gray, face)    for i, pt in enumerate(shape.parts()): #print('Part {}: {}'.format(i, pt)) pt_pos = (pt.x, pt.y) cv2.circle(img, pt_pos, 2, (255, 0, 0), 1) cv2.imshow('FaceDetect',img) if cv2.waitKey(1) & 0xFF == 27: break capInput.release() cv2.destroyAllWindows()
if __name__ == "__main__":  detect_face()

好啦,佳期如梦,双星良夜,在一个充满爱意的日子里,定位好女神常用的口红色号,和那个她来场华丽的邂逅吧!

关于我

更多信息可以点击关于我 , 非常希望和大家一起交流 , 共同进步

你和你的女神之间,差了一个OpenCV口红色号识别器相关推荐

  1. 你和你的女神之间,差了一个OpenCV口红色号识别器,android开发环境的搭建步骤

    MeanC=((int)(Mean_R/count),(int)(Mean_G/count),(int)(Mean_B/count)) return Me 番茄的颜色提取到了,那么和什么做比对呢? 当 ...

  2. 【情人节特辑】人工智能帮你识别口红色号!搞定女神不再难

    Git项目源码:https://github.com/DaMaiGit/artifact 花絮 今天是一年一度的情人节.都说女生的梳妆台上永远缺一支口红,在这个时候,给心仪的女神送一支适合她的口红,表 ...

  3. [转]苦逼男和女神之间的经典对话,亲身经历过的有木有啊,必须转。。。

    苦逼男和女神之间的经典对话,亲身经历过的有木有啊 "苦逼男和女神之间的经典对话,亲身经历过的有木有啊:女神啊,真是让我等苦逼男情何以堪啊..." 1.这张好漂亮啊/这张好可爱啊 苦 ...

  4. 计算两日期之间差多少天----日期格式为:yyyy-mm-dd

    计算两日期之间差多少天 日期格式为yyyy-mm-dd //计算日期差值的方法: //计算日期差值的方法: function getDaysBetween(dateString1, dateStrin ...

  5. 对话图森无人车CEO陈默:IPO,我们只差最后一个必要条件

    雷刚 发自 凹非寺  量子位 报道 | 公众号 QbitAI 谁是最有前景的无人车公司? 可能一千个人有一千零一种答案. 但谁会是最快冲刺IPO上市的无人车创业公司? (Almost)图森未来. 在接 ...

  6. 在2B和2C之间,还有一个2H(下)

    大家都说,2019年是5G元年,对这一新技术的讨论,从年初一直延续到了年底. 但驱动5G成功的商业场景到底是什么?至今,还没有争出个结论. 我用三篇文章,抛出我的几个观点,这是第3篇. 第一篇说的是: ...

  7. 产生0到1之间均匀分布的一个随机数原理与实现

    产生0到1之间均匀分布的一个随机数 实现的代码如下: double rnd1(r)double *r;{ int m;double s,u,v,p;s=65536.0; u=2053.0; v=138 ...

  8. 主机甲和主机乙之间已建立一个TCP连接,TCP最大段长为1000B。若主机甲的当前拥塞窗口为4000B,在主机甲向主机乙连续发送两个最大段后,成功收到主机乙发送的第一个段的确认段,确认段中通告的接收窗

    主机甲和主机乙之间已建立一个TCP连接,TCP最大段长为1000B.若主机甲的当前拥塞窗口为4000B,在主机甲向主机乙连续发送两个最大段后,成功收到主机乙发送的第一个段的确认段,确认段中通告的接收窗 ...

  9. mysql复杂查询示例_找到时间和内存复杂性之间的平衡-一个示例

    mysql复杂查询示例 by Anmol Uppal 通过Anmol Uppal 找到时间和内存复杂性之间的平衡-一个示例 (Finding the balance between time and ...

最新文章

  1. 树莓派 ROS 段错误
  2. 计算机书籍-Go语言并发之道
  3. java resources目录 编码_关于Java项目读取resources资源文件路径
  4. keepalived实现Tomcat服务双机热备
  5. [数据结构-严蔚敏版]P65离散事件模拟(银行客户的离散事件驱动模拟程序)
  6. P1375-小猫【卡特兰数】
  7. mysql 获取天数_MySQL获取某月份的天数
  8. blob没权限 ie_vuerouter 源码和动态路由权限分配
  9. 语言压缩zip win_主流压缩软件挨个尝试后,我选择了没有短板的全能压缩Bandizip...
  10. 从零开始的全栈工程师——html篇1.4
  11. 系统分析与设计第一次作业
  12. 微信公众号用户标签php,C#微信开发之微信公众号标签管理功能
  13. android p 小米6,小米6还能再战几年!将升级Android P
  14. win10误删的注册表能还原吗_教你手动还原Win10注册表?
  15. android脚本模拟器,android运行模拟器脚本(批处理)
  16. css 多行文字左对齐,纯CSS实现文字一行居中,多行左对齐的方法
  17. B. Boboniu Plays Chess(手速)
  18. win10内网穿透实现远程桌面连接
  19. 5G SCMA MPA算法
  20. iOS 优化 - 瘦身

热门文章

  1. 求职必看:后端工程师就业公司选择解析
  2. Xshell下载文件到本地
  3. photoshop中魔棒使用方法
  4. AQS: CLH 介绍
  5. python可以做ui吗_python做ui
  6. android七牛短视频sdk源码,使用七牛开发短视频
  7. DQN:Playing Atari with Deep Reinfocement Learning
  8. 查看变量内存的python内置函数是_这68个Python内置函数,建议你吃透
  9. cuda中pinned memory(page-locked memory)
  10. mvp的全称_打游戏抢了这么久的MVP,你竟不知道MVP的全称和由来?