顔認識のテストのためにスクレイピングでハーフタレントの画像収集してみた

# 目的

顔認識のため、適当な画像を集めたかったです。過去にYahoo画像検索からスクレイピングしたスクリプトを再度動かそうとしましたが、Yahoo画像検索のページやライブラリが変わっていたので、改めてメモを作り直しました。

今回はハーフタレントの画像を集めてみました。顔認識した話は別途記載します。

# 内容
1. スクレイピング&顔検出
   - 画像検索のスクレイピング
   - スクレイピング結果から顔検出してサイズを揃えて保存

2. 顔画像たちを目視で確認しながらラベル付け

# スクレイピング&顔検出
今回は｛ウエンツ,ユージ,JOY,ハリー杉山｝を検索した。なんとなく、学習難しそうなので。 max100枚として、フォルダを分けて保存していきました。
2022年版ということで、SnowManでやろうとしましたが、あまり詳しくないからか目視でのラベリングが大変そうだったので、今回もハーフタレントでまずは試しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599506/d29704ba-659a-ac15-433a-37467abc2334.png)

```python
if __name__ == '__main__':
    word     = ['JOY','ハリー杉山','ウエンツ瑛士','ユージ']
    dir_name = ['Joy','Harry','Uentsu','Yuji']
    # word     = ['岩本照'  ,'深澤辰哉'  ,'渡辺翔太' ,'向井康二','阿部亮平','目黒蓮', '宮舘涼太'  ,'佐久間大介','ラウール']
    # dir_name = ['Iwamoto','Fukazawa','Watanabe','Mukai' ,'Abe'    ,'Meguro','Miyadate','Sakuma'  ,'Raul']
    max_img = 100
    for i in range(word.__len__()):
        keyword = word[i]
        save_dir = './downloads/' + dir_name[i]
        print('keyword  = ' + keyword)
        print('save_dir = ' + save_dir)
        yahoo_scraping(keyword,max_img,save_dir)
```

コード全体はgithubに置く予定です。

## Yahoo画像検索のスクレイピング
サムネ画像もそこそこ大きい(200x200くらい)ので、こちらで画像を集めることにしました。~~1ページ20枚の画像を表示できるようなので、20枚の画像を確認したらページを送る。~~ 処理の流れは下記のイメージです。

- Yahoo画像へアクセス
- 画面最下部まで自動でスクロール
- 表示されたすべての画像URLを取得
- URL先から画像を検出
- 画像から顔を検出して保存

Selenium・WebDriverを使ってスクレイピングを行っています。[【画像あり】Selenium・WebDriverの準備手順](https://www.tomotaku.com/prepare-selenium-webdriver/) 

スクレイピングについては下記を参考にさせていただきました。
参考：[【コピペOK】Pythonによる画像スクレイピング【サンプルコード付き】](https://www.tomotaku.com/python-scraping-img/)

詳細は参考サイトに丁寧に記載されています。

### ※ バージョン依存あり
Seleniumのバージョンによってタグ取得のための記述が異なります。
```python
# 画像タグをすべて取得
# Selenium 3系
elements = driver.find_elements_by_tag_name("img") 

# Selenium 4系
from selenium.webdriver.common.by import By  
elements = driver.find_elements(By.TAG_NAME, "img") 
```

## スクレイピング

```python
def yahoo_scraping(word, max_img=20, save_dir='./downloads/'):
    # ディレクトリが存在しなければ作成する
    os.makedirs(save_dir,exist_ok=True)

    # Webdriverの設定
    options = Options()
    options.add_argument('--headless')  # UI無しで操作する
    chrome_service = fs.Service(executable_path=CHROMEDRIVER)
    driver = webdriver.Chrome(service=chrome_service,options=options)

    # yahoo画像へアクセス
    url = "https://search.yahoo.co.jp/image/search?p={}"
    driver.get(url.format(word+'+タレント'))  # 指定したURLへアクセス

    urls = []  # 画像URLを格納するリスト

    # 止まるまでスクロールする
    while True:
        prev_html = driver.page_source  # スクロール前のソースコード
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  # 最下部までスクロール
        sleep(1.0)  # 1秒待機
        current_html = driver.page_source  # スクロール後のソースコード

        # スクロールの前後で変化が無ければループを抜ける
        if prev_html != current_html:
            prev_html = current_html
        else:
            # 「もっと見る」ボタンがあればクリック
            try:
                # button = driver.find_element_by_class_name("sw-Button")
                button = driver.find_elements(By.CLASS_NAME, "sw-Button") # Selenium 4系
                button[0].click()
            except:
                break

    # 画像タグをすべて取得
    # elements = driver.find_elements_by_tag_name("img") # Selenium 3系
    elements = driver.find_elements(By.TAG_NAME, "img") # Selenium 4系

    # すべての画像URLを抜き出す
    for elem in elements:
        url = elem.get_attribute("src")
        if url not in urls:
            urls.append(url)  # urlをリストに追加する

    driver.close()  # driverをクローズする
    download_faceimgs(urls, save_dir, max_img)  # 顔画像をダウンロードする
```

## 複数の顔画像をダウンロード

URLから取得した画像をバイナリから配列の形に変形して、顔検出する関数に入力しています。顔検出の関数を使いまわすためにフォーマットを揃えてます。

```python
# 複数の顔画像のダウンロードを行う関数
def download_faceimgs(img_urls, save_dir, max_img=20):
    cnt_face = 0
    for i, url in enumerate(img_urls):
        file_name = f"{i}.png"  # 画像ファイル名
        save_img_path = os.path.join(save_dir, file_name)  # 保存パス

        r = requests.get(url, stream=True)
        if r.status_code == 200:
            # Binary -> np.asarray
            img_base = np.asarray(Image.open(io.BytesIO(r.content)))
            img_base = cv2.cvtColor(img_base, cv2.COLOR_RGBA2BGR)

            # Face Detection
            img_det, img_face, facerect = face_detect(img_base)

            if len(img_face) != 0: # 顔検出したらカウントアップして保存
                cnt_face = cnt_face + 1
                img_face = np.squeeze(img_face[0][:][:][:])
                cv2.imwrite(save_img_path, img_face)
                # cv2.imshow('face', img_face)  # imshow()で見たい画像を表示する

        if (cnt_face + 1) % 10 == 0 or (cnt_face + 1) == len(img_urls):
            print(f"{cnt_face + 1} / {len(img_urls)} done")
        if cnt_face >= max_img:
            return()
```


## 顔検出&リサイズ

OpenCVの検出器で顔検出して、リサイズして出力する関数です。過去に作ったものを使いまわしてます。

```python
import cv2 # OpenCV のインポート
import os
from datetime import datetime
import numpy as np

cascade     = cv2.CascadeClassifier('cascades/haarcascade_frontalface_default.xml')

# 顔の検出
def face_detect(frame,show_face=[], face_size=[180,180]):
    # そのままの大きさだと処理速度がきついのでリサイズ
    # frame = cv2.resize(frame, (int(frame.shape[1] * 0.7), int(frame.shape[0] * 0.7)))

    # 処理速度を高めるために画像をグレースケールに変換したものを用意
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # 顔検出 detecctMultiScale()は検出器のルール(cascade)に従って検出した結果をfacerectに返す関数
    facerect = cascade.detectMultiScale(
        gray,
        scaleFactor=1.11,
        minNeighbors=3,
        minSize=(20, 20)
    )

    # import pdb; pdb.set_trace()
    if len(facerect) != 0:
        # Resize 顔より少し広い範囲を指定
        mag = 1
        # facerect
        for i_det in range(facerect.shape[0]):
            facerect[i_det, 0] = int(facerect[i_det, 0] - facerect[i_det, 2] * (mag-1)*0.5)
            facerect[i_det, 1] = int(facerect[i_det, 1] - facerect[i_det, 3] * (mag-1)*0.5)
            facerect[i_det, 2] = int(facerect[i_det, 2] * mag)
            facerect[i_det, 3] = int(facerect[i_det, 3] * mag)

        for i_fc in range(facerect.shape[0]):
            x, y, w, h = facerect[i_fc]
            # 顔の部分
            face = frame[y: y + h, x: x + w]
            # くり抜いた顔の部分
            face_resize = cv2.resize(face, (int(face_size[0]), int(face_size[1])))
            face_resize = face_resize[np.newaxis,:,:,:]

            if i_fc == 0:
                show_face = face_resize
            else:
                show_face = np.concatenate([show_face,face_resize],0)

            # cv2.imshow('face', show_face_gray)  # imshow()で見たい画像を表示する
            # 顔検出した部分に枠を描画
            '''
            cv2.rectangle(
                frame,
                (x, y),
                (x + w, y + h),
                (255, 255, 255),
                thickness=2
            )
            '''
    return (frame,show_face,facerect)
```

# 画像のラベル付け
顔画像たちを目視で確認しながらフォルダ分け＝ラベル付けしていきます。関係ない画像も紛れ込んでいるので、”Other”のフォルダへと移動します。

単純にopencvで顔検出をしているだけなので、一つの画像に顔が複数ある場合には間違った画像を取得している場合もたくさんあります。

ウエンツの例。ほとんどウエンツだが、たまに小池徹平とか紛れ込みます。ウエンツは芸歴が長いので、幼少期の画像もたまに出てきます。個人的には最近のウエンツが一番かっこいいと思う。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599506/31e6233c-8b1b-5107-0073-129131c0b5b4.png)

ウエンツまでは調子よく分類していったが、ユージ、JOY…と続けていくと、ハーフ系タレントのゲシュタルト崩壊がおきてきて、そもそも目視でみてもわからなくなってきます。画像の解像度が悪いものも無理やり180x180へ拡大しているものもあり、画質がよくないものも含まれます。JOYの中にユージが混ざっていてもわからない。

（最初は安全に嵐とかSMAPとかTOKIOでやればよかったかも。）

画像100枚X4人分をざっくり見るのに10分くらい。これくらいなら目視でなんとかできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599506/89333490-f298-8d4f-abcf-bc787b26cff4.png)

# まとめ
画像収集はうまくできたので、これから顔認識のテストをしていきます。
Google画像検索のスクレイピングも過去に試しましたが、うまくいかなかったので、今回もYahoo画像検索で試しました。もっとスクレイピングに適した画像検索があるかもしれません。([ソースコードとvenv](https://github.com/iwatadive28/Yahoo_scraping))