你和你的女神之間,差了一個OpenCV口紅色號識別器

前不久,身邊一朋友要送女友生日禮物,送完就被女友吐槽了一頓。


什麼???今年又送了死亡芭比粉???情人節也是???
 
若是說彩妝中最受女生歡迎的是哪個,相信全部人都會脫口而出:口紅。尤爲是有女友的男孩子更能明白,有了口紅就不要男票的心痛吧。因此,大家給女友買口紅,就選擇死亡芭比粉是嗎?

對於廣大「鋼鐵直男」的程序員來講,送什麼禮物給女友一直是個世紀難題程序員

其實哄女友開心最深的套路就是花式送口紅,就問誰抵擋得住啊啊啊啊......算法

「沒有什麼問題是一支口紅解決不了的,若是有,那就兩支。」因而,直男們紛紛開始各類買口紅、送口紅……數據庫

畢竟李佳琦一句"OMG買它」,女友披頭散髮搶購,錢包就空了一半。

可是,口紅色號千千萬,選對了牌子才成功了一半。json

快樂橙、傷心紫,姨媽紅,雞屎綠…直男眼裏沒什麼區別的顏色,在女生眼裏各類色調、質地細微的區別都能分析一清二楚。數組

那麼,對於直男來講,怎麼才能搞清楚如此多的口紅色號呢?數據結構

我耗費一毫米髮際線,琢磨了一下,作出了一個口紅色號識別器,但願能幫你們在關鍵時刻把深入的革命友誼再昇華一下。app

先來看看效果。讓咱們假設,小姐姐發來了一張美妝博主的美照,並暗示你,「人家也喜歡這個顏色。」

這個時候,用咱們的口紅色號識別器,就能定位嘴脣,並迅速給出它的顏色隸屬哪家品牌的哪一個色號。

OMG!簡直比李佳琦還準確!
dom

好啦,廢話很少說,立刻開始教學時間!ide

要想識別口紅色號,先得讓機器知道到底都有哪些顏色。oop

聽櫃姐介紹,紅色系有:「草莓紅、鐵鏽紅、楓葉紅...」,其餘還有「豆沙色、吃土色、番茄色...

世界觀還未創建徹底就要開始土崩瓦解,這看着有區別嗎?「豆沙色最爲百搭,橘調的番茄色比較顯白...」眼前的黑不是黑,你說的紅是什麼紅?

還好,在萬能的 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()

好啦,佳期如夢,雙星良夜,在一個充滿愛意的日子裏,定位好女神經常使用的口紅色號,和那個她來場華麗的邂逅吧!

關於我

更多信息能夠點擊關於我 , 很是但願和你們一塊兒交流 , 共同進步

相關文章
相關標籤/搜索