直男福利!手把手教你做一隻口紅色號識別器,秒變李佳琦

大資料文摘發表於2019-08-07

直男福利!手把手教你做一隻口紅色號識別器,秒變李佳琦

大資料文摘編輯組出品
技術實現:寧靜 
七夕將至,送禮時節。直男送禮,首選口紅。
畢竟李佳琦一句"OMG買它”,女朋友披頭散髮搶購,錢包就空了一半。
直男福利!手把手教你做一隻口紅色號識別器,秒變李佳琦
但是,口紅色號千千萬,選對了牌子才成功了一半。
快樂橙、傷心紫,姨媽紅,雞屎綠…直男眼裡沒什麼區別的顏色,在女生眼裡各種色調、質地細微的區別都能分析一清二楚。
直男福利!手把手教你做一隻口紅色號識別器,秒變李佳琦
那麼,對於直男來說,怎麼才能搞清楚如此多的口紅色號呢?
文摘菌耗費一毫米髮際線,琢磨了一下,做出了一個口紅色號識別器,希望能幫大家在七夕把深刻的革命友誼再昇華一下。
先來看看效果。
讓我們假設,七夕前夕,小姐姐發來了一張美妝博主的美照,並暗示你,“人家也喜歡這個顏色。
直男福利!手把手教你做一隻口紅色號識別器,秒變李佳琦
圖片來自網路
這個時候,用我們的口紅色號識別器,就能定位嘴唇,並迅速給出它的顏色隸屬哪家品牌的哪個色號。
直男福利!手把手教你做一隻口紅色號識別器,秒變李佳琦
OMG!簡直比李佳琦還準確!
直男福利!手把手教你做一隻口紅色號識別器,秒變李佳琦



好啦,廢話不多說,馬上開始教學時間!

來自Github的口紅色號宇宙

要想識別口紅色號,先得讓機器知道到底都有哪些顏色。
文摘菌聽櫃姐介紹,紅色系有:“草莓紅、鐵鏽紅、楓葉紅...”,其他還有“豆沙色、吃土色、番茄色...”
直男福利!手把手教你做一隻口紅色號識別器,秒變李佳琦


世界觀還未建立完全就要開始土崩瓦解,這看著有區別嗎?
“豆沙色最為百搭,橘調的番茄色比較顯白...”
眼前的黑不是黑,你說的紅是什麼紅?
直男福利!手把手教你做一隻口紅色號識別器,秒變李佳琦
還好,在萬能的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個檢測點來思考人生。
直男福利!手把手教你做一隻口紅色號識別器,秒變李佳琦

聖羅蘭官網#842C71口紅

標記的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,對應的顏色如下:
       直男福利!手把手教你做一隻口紅色號識別器,秒變李佳琦       
明顯看到資料集儲存的顏色和實際照片的顏色是有些許誤差的,而在本文演算法實現過程中,又不可避免的有以下誤差:
  1. 嘴唇區域擷取不可避免會擷取到皮膚中的一部分顏色,雖然演算法已經將那種可能降到最低;
  2. 顏色提取上,雖然擷取多個嘴唇圖片求平均值,但是本身的提取演算法還是和實際值稍有偏差;
  3. RGB顏色相似度比對的演算法也不夠精確;
  4. 最最重要的是,照片必須是原圖,而且光線要自然,加了濾鏡的圖是怎麼也不可能識別出來的。
以上種種,使得讓計算機快速高效地識別不同的口紅色號還是有困難的,原來計算機有時候也會很直男。

文末福利:實時人像口紅色號預測

看到這兒,可能很多讀者朋友想實時地試一下能不能讓計算機判斷自己的口紅色號,這對於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()

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

不說了,文摘菌去下單口紅了!

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31562039/viewspace-2652990/,如需轉載,請註明出處,否則將追究法律責任。

相關文章