Keras+Tensorflow で猫の毛色分類（サビ猫は雑誌の表紙を飾ったのか？）1



### サビ猫かわいいよね？
でも、世にあふれてる猫雑誌にサビ猫いなくない？　特に表紙

#### もくじ
1. **学習用画像を集める（ココ）**
2. [猫のご尊顔を検出](https://qiita.com/piroyon/items/3ece37d9111529e3b941)
3. [学習](https://qiita.com/piroyon/items/b3822e4e6d329f793061)
3. [猫雑誌の表紙画像を集めて判定](https://qiita.com/piroyon/items/d1c6da27ff3f01504039)


#### 開発環境
Mac Pro (Late 2013) 3.5GHz 6Core XeonE5, 64GB DDR3, OSX Yosemite 10.10.5
Python 3.6.6
Tensorflow 1.9.0
Keras 2.2.0

## 画像集め
- [Bingの画像検索APIを使って画像を大量に収集する](https://qiita.com/ysdyt/items/49e99416079546b65dfc)
- [スクレイピング初心者がpythonでかわいい猫ちゃん画像をコマンド一発でネットから収集してみた](https://karaage.hatenadiary.jp/entry/2017/08/23/073000)

を参考に、Bing と Excite 2つのサイトから画像を集めた（全コードは末尾に）。
　分類カテゴリともなる検索ワードは「cat」に続けて14通りの毛色の単語を投入。（例：cat redtabby）
　Bingの画像検索では「写真のみ」等を指定できるので、パラメータに `'imageType': 'Photo'` を追加。（参考）[Image Search API v7 reference](https://docs.microsoft.com/en-us/rest/api/cognitiveservices/bing-images-api-v7-reference)
　後者ではYahooの画像検索を使っているが、8月現在Yahoo側の仕様変更で動かないので、img_url_list 関数部分をExcite 検索用に書き換えて利用させていただいた。
　Exciteの画像検索結果表示は1ページ20枚、呼び出し側でループを作って、ページ数を引数（page）で渡して使う仕様。検索ワード（引数：word）が複数の場合は「＋」で連結しといて渡すこと。（例：cat+sealpoint）

```python 
def img_url_list(word, page):
    url = 'https://imagesearch.excite.co.jp/?q={}&page='.format(quote(word))
    url += str(page)
    byte_content, _ = fetcher.fetch(url)
    structured_page = BeautifulSoup(byte_content.decode('UTF-8'), 'html.parser')
    img_link_elems = structured_page.find_all('a', attrs={'rel': 'lightbox[result]'})
    img_urls = [e.get('href') for e in img_link_elems if e.get('href').startswith('http')]
    img_urls = list(set(img_urls))
    return img_urls
```


　前者の記事にあるように、Bing、Excite 共に画像URLをハッシュ化したものをファイル名として保存して
重複を排除した。
　両サイトからダウンロードした画像を同じカテゴリフォルダに保存して準備完了。
　カテゴリ分けは厳密にやったらキリがないので、黒、白、茶虎、サバ虎、銀虎、鉢割、虎の鉢割、三毛、シャム風、森永（lynxpointで検索...）、クリーム、サビ、に加えて、猫種を入れた方がいいだろうと思われたアビシニアンとロシアンブルー　計14種類
　下記のはカテゴリ：white にゲスト参加していただいたうちのねこ。キャワワ
![スクリーンショット 2018-08-30 9.14.53.png](https://qiita-image-store.s3.amazonaws.com/0/45576/c7c01397-d60b-2b1f-35eb-25ad266035df.png)

共通の使い方：

- キーワード「cat」は既にベタ書きしてあるので、　猫の毛色キーワードのみ引数で与える
- catimage というディレクトリ下に、毛色名のディレクトリができて画像が保存される
- 対象は jpg, png のみ。png は jpgに変換して保存

#####bingの画像収集

- bing api key を取得して、変数 bing_api_key に入れる

`python3 bing_image_collector.py redtabby`

```python3:bing_image_collector.py
import sys
import os
import math
import urllib
import hashlib
import sha3
import requests
import time

args = sys.argv
query = 'cat '  #"cat is default query, plz input keyword color"
args.pop(0)
length = len(args)
color1 = args[0]
for idx, color in enumerate(args):
    query = query + color
    if (idx != length-1):
        query = query + ' '

def make_dir(path):
    if not os.path.isdir(path):
        os.mkdir(path)

def split_filename(f):
    split_name = os.path.splitext(f)
    file_name =split_name[0]
    extension = split_name[-1].replace(".","")
    return file_name,extension

def download_img(path,url):
    make_dir(path)
    _,extension  = split_filename(url)
    if extension.lower() in ('jpg','jpeg','png'):
        encode_url = urllib.parse.unquote(url).encode('utf-8')
        hashed_name = hashlib.sha3_256(encode_url).hexdigest()
        full_path = os.path.join(path,hashed_name + '.' + extension.lower())

        r = requests.get(url)
        if r.status_code == requests.codes.ok:
            with open(full_path,'wb') as f:
                f.write(r.content)
                if (extension.lower() == 'png'):
                    png_im = Image.open(full_path).convert('P')
                    rgb_im = png_im.convert('RGB')
                    jpg_file = os.path.join(path, hashed_name + '.jpg')
                    rgb_im.save(jpg_file, quality=30)
                    os.remove(full_path)
            print('saved image...{}'.format(url))
        else:
            print("HttpError:{0}  at{1}".format(r.status_code,url))

if __name__ == '__main__':
    bing_api_key = 'xxxxxxxxxxxxxxxx'  #your bing api key
    save_dir_path = './catimage/' + color1
    make_dir(save_dir_path)

    num_imgs_required = 300 # Number of images you want.
    num_imgs_per_transaction = 50 # default 30, Max 150 images
    offset_count = math.floor(num_imgs_required / num_imgs_per_transaction)

    url_list = []
    correspondence_table = {}

    headers = {
        'Content-Type': 'multipart/form-data',
        'Ocp-Apim-Subscription-Key': bing_api_key
    }

    bingurl = "https://api.cognitive.microsoft.com/bing/v7.0/images/search"
    for offset in range(int(offset_count)):
        params = urllib.parse.urlencode({
            'q': query,
            'mkt': 'ja-JP',
            'imageType': 'Photo',
            'count': num_imgs_per_transaction,
            'offset': offset * num_imgs_per_transaction # increment offset by 'num_imgs_per_transaction' (for example 0, 150, 300)
        })
        try:
            response = requests.get(bingurl, headers=headers, params=params)
            data = response.json()
        except Exception as err:
            print("[Errno {0}] {1}".format(err.errno, err.strerror))
        else:
            for values in data['value']:
                img_url = values['contentUrl']
                try:
                    download_img(save_dir_path,img_url)
                except Exception as e:
                    print("failed to download image at {}".format(img_url))
                    print(e)
            time.sleep(1)
```

#####exciteの画像収集

- MY_EMAIL_ADDR にメアドを入れよう
- ページ数 x 20枚の画像が取れる。コメント #how many pages(x20 images get) のところ

`python3 excite_image.py redtabby`

```python:excite_image.py
import os
import sys
from time import time, sleep
from urllib.request import urlopen, Request
from urllib.parse import quote
from bs4 import BeautifulSoup
import hashlib
import sha3
import urllib
import requests
from PIL import Image

MY_EMAIL_ADDR = 'a@b.c'  #your mail address

class Fetcher:
    def __init__(self, ua=''):
        self.ua = ua

    def fetch(self, url):
        req = Request(url, headers={'User-Agent': self.ua})
        try:
            with urlopen(req, timeout=3) as p:
                b_content = p.read()
                mime = p.getheader('Content-Type')
        except:
            sys.stderr.write('Error in fetching {}\n'.format(url))
            sys.stderr.write(traceback.format_exc())
            return None, None
        return b_content, mime

fetcher = Fetcher(MY_EMAIL_ADDR)

def make_dir(path):
    if not os.path.isdir(path):
        os.mkdir(path)

def split_filename(f):
    split_name = os.path.splitext(f)
    file_name =split_name[0]
    extension = split_name[-1].replace(".","")
    return file_name,extension

def fetch_and_save_img(word):
    kl = word.split('+')
    path = 'catimage/' + kl[1]
    make_dir(path)
    for j in range(5): #how many pages(x20 images get) 
        h = j + 1
        for i, img_url in enumerate(img_url_list(word, h)):
            sleep(0.1)
            _,extension  = split_filename(img_url)
            if extension.lower() in ('jpg','jpeg','png'):
                encode_url = urllib.parse.unquote(img_url).encode('utf-8')
                hashed_name = hashlib.sha3_256(encode_url).hexdigest()
                result_file = os.path.join(path, hashed_name + '.' + extension.lower())
                r = requests.get(img_url)
                if r.status_code == requests.codes.ok:
                    with open(result_file, mode='wb') as f:
                        f.write(r.content)
                        print('fetched', img_url)
                        if (extension.lower() == 'png'):
                            png_im = Image.open(result_file).convert('P')
                            rgb_im = png_im.convert('RGB')
                            jpg_file = os.path.join(path, hashed_name + '.jpg')
                            rgb_im.save(jpg_file, quality=30)
                            os.remove(result_file)

def img_url_list(word, page):
    url = 'https://imagesearch.excite.co.jp/?q={}&page='.format(quote(word))
    url += str(page)
    print(url)
    byte_content, _ = fetcher.fetch(url)
    structured_page = BeautifulSoup(byte_content.decode('UTF-8'), 'html.parser')
    img_link_elems = structured_page.find_all('a', attrs={'rel': 'lightbox[result]'})
    img_urls = [e.get('href') for e in img_link_elems if e.get('href').startswith('http')]
    img_urls = list(set(img_urls))
    return img_urls

if __name__ == '__main__':
    args = sys.argv
    query = 'cat+'
    args.pop(0)
    length = len(args)
    color1 = args[0]
    for idx, color in enumerate(args):
        query = query + color
        if (idx != length-1):
            query = query + '+'
    fetch_and_save_img(query)
```