肌の色を認識しよう！


#　理論
***この部分の内容は、[この論文](https://arxiv.org/ftp/arxiv/papers/1708/1708.02694.pdf)からまとめて翻訳されています。***

##　色空間

色空間は、色情報を3つまたは4つの異なる色成分として表す数学モデルです。皮膚の検出には、さまざまな色空間を利用できます。 それらは、RGBベースの色空間（RGB、正規化されたRGB）、色相ベースの色空間（HSI、HSV、およびHSL）、輝度ベースの色空間（YCBCr、YIQ、およびYUV）です。

### RGB（Red-Green-Blue：赤ー緑ー青）
RGB色空間は広く使用されており、通常、デジタル画像を保存および表現するためのデフォルトの色空間です。 原色である赤、緑、青の3つのコンポーネントで構成されています。 3つの基本色を混合することにより、任意の色を得ることができます。

これは、赤、緑、青の値の線形結合によって色が取得される付加的な色空間です。
3つのチャネルは、表面に当たる光の量によって相関しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521590/36e29c9c-d74b-ceb1-43f1-0f9e160b9fef.png)

### HSV（Hue Saturation Value：色相ー彩度ー明度）
色相（H）が0から1.0まで変化すると、対応する色は赤から黄色、緑、シアン、青、マゼンタを経て赤に戻ります。 彩度（S）が0から1.0まで変化すると、対応する色（色相）は不飽和（灰色の陰影）から完全に飽和（白成分なし）まで変化します。 値（V）または明るさが0から1.0まで変化すると、対応する色がますます明るくなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521590/7d0148d8-9894-6aa3-772d-4adda26fee40.png)



### YCbCr（Luminance, Chrominance）
YCbCrは、エンコードされた非線形RGB信号です。 色は、RGB値の加重和として構築された輝度（非線形RGBから計算された輝度）で表されます。
このフォーマットでは、輝度情報は単一の成分（Y）として保存され、クロミナンス情報は2つの色差成分（CbとCr）として保存されます。この表現により、冗長な色情報を簡単に取り除くことができるため、JPEG、MPEG1、MPEG2、MPEG4などの画像およびビデオ圧縮規格で使用されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521590/d58a0720-74ac-0c8e-1cf9-a19972e72d63.png)



## 人間の肌色
### 著者が提案した皮膚検出アルゴリズム

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521590/c6968375-4279-6f3e-7d9b-bc5af091b768.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521590/0871a308-eb7c-c006-1302-1b6d3b177280.png)

以上のアルゴリズムについていくつかまだわからなくて、実装も難しそうなので、このもっと簡単な方法を使ってみます
https://github.com/CHEREF-Mehdi/SkinDetection

```
    HSV: 0<=H<=17 and 15<=S<=170 and 0<=V<=255
			and
    YCrCb: 0<=Y<=255 and 135<=Cr<=180 and 85<=Cb<=135
```

#　実装

## 目的
![test.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521590/f1af9824-0875-cb87-b599-689f5b96ae03.jpeg)
以上の画像から肌の色で指先を認識することです。

## 環境

```
Windows 10
OpenCV 3.4.2
Python 3.7.9
```

## コード

### HSVフィルター
```python
img=cv2.imread("a.jpg")
img_YCrCb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
HSV_mask = cv2.inRange(img_HSV, (0, 15, 0), (17,170,255)) 
HSV_mask = cv2.morphologyEx(HSV_mask, cv2.MORPH_OPEN, np.ones((3,3), np.uint8))
cv2.imshow("HSV.jpg", HSV_mask)
```
HSVフィルターの結果
![1_HSV.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521590/0e1a94b0-a3b8-05c6-91b6-9e90999fec81.jpeg)


### YCrCbフィルター
```python
img=cv2.imread("a.jpg")
img_YCrCb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
YCrCb_mask = cv2.inRange(img_YCrCb, (0, 135, 85), (255,180,135)) 
YCrCb_mask = cv2.morphologyEx(YCrCb_mask, cv2.MORPH_OPEN, np.ones((3,3), np.uint8))
cv2.imshow("YCbCr.jpg",YCrCb_mask)
```
YCrCbフィルターの結果
![2_YCbCr.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521590/ca700060-2292-1562-c155-0ae1b2385499.jpeg)

見えるようにYCrCbフィルターの方はよく肌の色をにんしきできます。

## OpenCVの輪郭（[リンク](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_contours/py_table_of_contents_contours/py_table_of_contents_contours.html#table-of-content-contours)）

```python

def apply_contours(contour_img):
    ret, thresh = cv2.threshold(contour_img, 127, 255, 0)
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[-2:]
    #　一番大きい輪郭を選ぶ
    cnt = max(contours, key = cv2.contourArea)
    #　輪郭の中最上の点を選ぶ
    topmost = tuple(cnt[cnt[:,:,1].argmin()][0])    
    contour_img = cv2.drawContours(img, [cnt], 0, (0,255,0), 3)
    contour_img = cv2.circle(contour_img , topmost, radius=5, color=(0, 0, 255), thickness=-1)
    return contour_img
```

輪郭とYCrCbフィルターの結果：
![2_YCbCr.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521590/b1582623-f3f1-33ff-ce69-0f97cdde2bd5.jpeg)

## 最後のコード
```python
import cv2
import numpy as np


img=cv2.imread("WIN_20201203_01_38_27_Pro.jpg")

img_YCrCb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
YCrCb_mask = cv2.inRange(img_YCrCb, (0, 135, 85), (255,180,135)) 
YCrCb_mask = cv2.morphologyEx(YCrCb_mask, cv2.MORPH_OPEN, np.ones((3,3), np.uint8))


def apply_contours(contour_img):
    ret, thresh = cv2.threshold(contour_img, 127, 255, 0)
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[-2:]
    #　一番大きい輪郭を選ぶ
    cnt = max(contours, key = cv2.contourArea)
    #　輪郭の中最上の点を選ぶ
    topmost = tuple(cnt[cnt[:,:,1].argmin()][0])    
    contour_img = cv2.drawContours(img, [cnt], 0, (0,255,0), 3)
    contour_img = cv2.circle(contour_img , topmost, radius=8, color=(0, 0, 255), thickness=-1)
    return contour_img

YCrCb_result = apply_contours(YCrCb_mask)

#show results
# cv2.imshow("Show image",YCrCb_result)
cv2.imwrite("YCbCr_result.jpg",YCrCb_result)
cv2.waitKey(0)
cv2.destroyAllWindows() 
```

Happy coding!