Help us understand the problem. What is going on with this article?

PythonでBing Image Search API v7を使って画像収集する

More than 1 year has passed since last update.

APIのバージョンが変わったからなのか、ちょっと手間取ったので共有します。
なお、当方はPython初学者 & Azure初心者です:bow_tone1:

やったこと

  • 機械学習のために画像収集した
    • 1000枚くらい欲しくて、調べた感じ Bing Image Search が良さそうだったのでこれを使うことにしました。
    • Yahoo、Bing、Googleでの画像収集事情まとめを参考に決めました。
    • 現在はAzureの無料試用版なので無料で使えてます。

環境

  • Ubuntu 16.04
  • Python 3.6

事前準備

事前準備はBingの画像検索APIを使って画像を大量に収集するを参考に進めました。

  • Azureアカウント作成
  • Azure上にAPIリソースを作成
    • 価格レベルはS1にしました。
    • 料金表を見る限り、S3の方が安そう。
  • APIエンドポイントURLとKeyを取得
    • エンドポイントは作成したサブスクリプションの Overview メニューで確認できます。
    • Keyは作成したサブスクリプションの Keys メニューで確認できます。

画像収集コード

実装はHow to (quickly) build a deep learning image datasetを参考しました。(ほぼそのままです)
私はJupyterNotebook上で実行しています。

from requests import exceptions
import argparse
import requests
import cv2
import os

API_KEY = "<取得したAPIKey>"
MAX_RESULTS = 100
GROUP_SIZE = 50

# 取得したエンドポイントURL
URL = "https://api.cognitive.microsoft.com/bing/v7.0/images/search"
OUTPUT = '<画像を保存するディレクトリ>'

if not os.path.isdir(OUTPUT):
    os.mkdir(OUTPUT)

EXCEPTIONS = set([IOError, FileNotFoundError,
    exceptions.RequestException, exceptions.HTTPError,
    exceptions.ConnectionError, exceptions.Timeout])

term = '<検索語>'
headers = {"Ocp-Apim-Subscription-Key" : API_KEY}
params = {"q": term, "offset": 0, "count": GROUP_SIZE, "imageType":"Photo", "color":"ColorOnly"}

# make the search
print("[INFO] searching Bing API for '{}'".format(term))
search = requests.get(URL, headers=headers, params=params)
search.raise_for_status()

# grab the results from the search, including the total number of
# estimated results returned by the Bing API
results = search.json()
estNumResults = min(results["totalEstimatedMatches"], MAX_RESULTS)
print("[INFO] {} total results for '{}'".format(estNumResults,
    term))

# initialize the total number of images downloaded thus far
total = 0

# loop over the estimated number of results in `GROUP_SIZE` groups
for offset in range(0, estNumResults, GROUP_SIZE):
    # update the search parameters using the current offset, then
    # make the request to fetch the results
    print("[INFO] making request for group {}-{} of {}...".format(
        offset, offset + GROUP_SIZE, estNumResults))
    params["offset"] = offset
    search = requests.get(URL, headers=headers, params=params)
    search.raise_for_status()
    results = search.json()
    print("[INFO] saving images for group {}-{} of {}...".format(
        offset, offset + GROUP_SIZE, estNumResults))
    # loop over the results
    for v in results["value"]:
        # try to download the image
        try:
            # make a request to download the image
            print("[INFO] fetching: {}".format(v["contentUrl"]))
            r = requests.get(v["contentUrl"], timeout=30)

            # build the path to the output image
            print
            ext = v["contentUrl"][v["contentUrl"].rfind("."):v["contentUrl"].rfind("?") if v["contentUrl"].rfind("?") > 0 else None]
            p = os.path.sep.join([OUTPUT, "{}{}".format(
                str(total).zfill(8), ext)])

            # write the image to disk
            f = open(p, "wb")
            f.write(r.content)
            f.close()

        # catch any errors that would not unable us to download the
        # image
        except Exception as e:
            # check to see if our exception is in our list of
            # exceptions to check for
            if type(e) in EXCEPTIONS:
                print("[INFO] skipping: {}".format(v["contentUrl"]))
                continue
        # try to load the image from disk
        image = cv2.imread(p)

        # if the image is `None` then we could not properly load the
        # image from disk (so it should be ignored)
        if image is None:
            print("[INFO] deleting: {}".format(p))
            os.remove(p)
            continue

        # update the counter
        total += 1

今回は を検索してちゃんと以下のような画像を得ることができました。

00000001.jpg

その他Tips

  • APIのリクエストパラメータで使いそうなものをピックアップして説明します。リファレンスはここです。
    • count レスポンスで返される画像の個数です。デフォルト35で最大150です。
    • imageType 画像の種類を指定できます。私は Photo に指定しました。 AnimatedGifClipartLinePhotoTransparentが指定できるようです。
    • color 色についても指定できるようです。後処理が手間だったので私は ColorOnly を指定しました。
    • license 今回は指定してないですが、仕事で使うときは指定したほうが良いかもです。ShareCommercially とかを指定する感じでしょうか。

並列化してみました

↑のコードを実行すると、私の環境では1000件保存するのに20〜30分くらい掛かりました。
遅いなぁと思ったのでさくっと並列処理に変えてみました。ざっくりN倍早くなるはずです。(実際8分くらいになりました

...

from joblib import Parallel, delayed

...

def request_and_save(offset):
    count = 0
    # update the search parameters using the current offset, then
    # make the request to fetch the results
    print("[INFO] making request for group {}-{} of {}...".format(
        offset, offset + GROUP_SIZE, estNumResults))
    params["offset"] = offset
    search = requests.get(URL, headers=headers, params=params)
    search.raise_for_status()
    results = search.json()
    print("[INFO] saving images for group {}-{} of {}...".format(
        offset, offset + GROUP_SIZE, estNumResults))
    # loop over the results
    for v in results["value"]:
        # try to download the image
        try:
            # make a request to download the image
            print("[INFO] fetching: {}".format(v["contentUrl"]))
            r = requests.get(v["contentUrl"], timeout=30)

            # build the path to the output image
            print
            ext = v["contentUrl"][v["contentUrl"].rfind("."):v["contentUrl"].rfind("?")  if v["contentUrl"].rfind("?") > 0 else None]
            p = os.path.sep.join([OUTPUT, "{}_{}{}".format(
                offset, str(count).zfill(8), ext)])

            # write the image to disk
            f = open(p, "wb")
            f.write(r.content)
            f.close()

        # catch any errors that would not unable us to download the
        # image
        except Exception as e:
            # check to see if our exception is in our list of
            # exceptions to check for
            if type(e) in EXCEPTIONS:
                print("[INFO] skipping: {}".format(v["contentUrl"]))
                continue
        # try to load the image from disk
        image = cv2.imread(p)

        # if the image is `None` then we could not properly load the
        # image from disk (so it should be ignored)
        if image is None:
            print("[INFO] deleting: {}".format(p))
            os.remove(p)
            continue

        # update the counter
        count += 1

# 4CPUで並列化
Parallel(n_jobs=4)([delayed(request_and_save)(offset) for offset in range(0, estNumResults, GROUP_SIZE)])

ファイルの命名規則が変わってますが気にしない!

m-shimao
福岡在住。機械学習エンジニア。AWS SAP・MLS。趣味はKaggleで、現在ソロ銀1。福岡でKaggleもくもく会を主催しています。
fusic
個性をかき集めて、驚きの角度から世の中をアップデートしつづける。
https://fusic.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした