こんにちは!AidemyのATcatです。
皆さんはInstagram(以下、インスタ)を使うことはありますか? 私はインスタを使って猫の画像を眺めることがよくあるのですが、猫の画像を探していると下のように猫以外の画像も混じってしまうことがよくあります。
インスタ側のシステムを変更することはできませんし、専用のアプリケーションを作成するには時間が足りません。そこで猫の画像だけみたい私は、インスタで”猫”とされている画像を取得して、猫の画像のみを抽出できるようなシステムを作ってみました。
物体検出
今回、猫の画像を抽出するために物体検出を用いました。まずは、この物体検出という技術について簡単に説明します。
画像中の着目したい物体があるとき、画像全体における特徴から「何が写っているか」のみを識別する技術を画像認識といいます。一方、「どこに何が写っているか」までを識別する技術が、物体検出です。
画像中に含まれる物体について、物体中、注目すべき物体が「何であるか」ということと、その物体が「どこにあるのか」まで特定し、バウンディングボックスという矩形によって表します。セマンティックセグメンテーションという技術もありますが、これはピクセルごとに分類するもので、より複雑なものとなります。
今回は、Googleの事前学習済みモデルを用いての実装を行いました。その理由としては、一からモデルを構築し学習するには、データセットの用意や学習時間、適切なクラス数の設定などに膨大な時間がかかること、また、業界では事前学習済みモデルを利用されることが非常に多いためです。
事前準備
まずは、インスタから猫の画像を集めるために#猫と#catのハッシュタグから画像収集を行うことにしました。その際、Instagram ScraperというAPIを用いました。
pip install instagram-scraper
として、まずpipでインストールを行います。
Instagram Scraperでは、特定のユーザーの投稿を取得することや、指定したハッシュタグで投稿されている画像や動画を取得することができます。
今回は次のように実行しました。
insta.sh
instagram_login_user=''
instagram_login_pass=''
target_tag='cat'
instagram-scraper \
--login_user $instagram_login_user \
--login_pass $instagram_login_pass \
--tag $target_tag \
--media-types image \
--maximum 100 \
--latest \
取得する数を200として設定しました。
実装
次に、取得した画像を、物体検出によって猫であるか判別します。
ここでは、Googleの事前学習済みモデルであるFaster R-CNNとSSDを、Tensorflow Hubを通して、Google Colaboratoryを利用して実装を行いました。
今回、下記のサイトを参考に実装しました。
https://qiita.com/code0327/items/3b23fd5002b373dc8ae8
ここでの流れとしては、事前学習済みモデルをTensorflow Hubを通して取得して定義し、インスタで取得した猫の画像に対して物体検出を行います。その後、猫を検出した場合にのみ検出結果を示す画像を出力するようにします。
まず、インポートと学習済みモデルの選択をします。
import tensorflow as tf
import tensorflow_hub as hub
import os
import glob
import time
import numpy as np
import matplotlib.patheffects as pe
import matplotlib.pyplot as plt
import tempfile
from six.moves.urllib.request import urlopen
from six import BytesIO
import numpy as np
from PIL import Image
from PIL import ImageColor
from PIL import ImageDraw
from PIL import ImageFont
from PIL import ImageOps
module_handle = 'https://tfhub.dev/google/faster_rcnn/openimages_v4/inception_resnet_v2/1'
detector = hub.load(module_handle).signatures['default']
物体検出を行った結果の画像化は次のようにします。
def showImage(img, r, imgfile, min_score=0.1):
fig = plt.figure(dpi=150,figsize=(8,8))
ax = plt.gca()
ax.tick_params(axis='both', which='both', left=False,
labelleft=False, bottom=False, labelbottom=False)
ax.imshow(img)
decode = np.frompyfunc( lambda p : p.decode("ascii"), 1, 1)
boxes = r['detection_boxes']
scores = r['detection_scores']
class_names = decode( r['detection_class_entities'] )
n = np.count_nonzero(scores >= min_score)
class_set = np.unique(class_names[:n])
colors = dict()
cmap = plt.get_cmap('tab10')
for i, v in enumerate(class_set):
colors[v] =cmap(i)
img_w = img.shape[1]
img_h = img.shape[0]
for i in reversed(range(n)):
text = f'{class_names[i]} {100*scores[i]:.0f}%'
color = colors[class_names[i]]
y1, x1, y2, x2 = tuple(boxes[i])
y1, y2 = y1*img_h, y2*img_h
x1, x2 = x1*img_w, x2*img_w
r = plt.Rectangle(xy=(x1, y1), width=(x2-x1), height=(y2-y1),
fill=False, edgecolor=color, joinstyle='round',
clip_on=False, zorder=8+(n-i) )
ax.add_patch( r )
t = ax.text(x1+img_w/200, y1-img_h/300, text, va='bottom', fontsize=6, color=color,zorder=8+(n-i))
t.set_path_effects([pe.Stroke(linewidth=1.5,foreground='white'), pe.Normal()])
fig.canvas.draw()
r = fig.canvas.get_renderer()
coords = ax.transData.inverted().transform(t.get_window_extent(renderer=r))
tag_w = abs(coords[0,0]-coords[1,0])+img_w/100
tag_h = abs(coords[0,1]-coords[1,1])+img_h/120
r = plt.Rectangle(xy=(x1, y1-tag_h), width=tag_w, height=tag_h,
edgecolor=color, facecolor=color,
joinstyle='round', clip_on=False, zorder=8+(n-i))
ax.add_patch( r )
plt.savefig('/content/save/'+imgfile)
plt.close()
min_score以上の信頼度を出したものに対して、矩形によって囲みローカライズするようにしています。
最後に、検出を行う関数の定義を行います。
import time
import numpy as np
import PIL.Image as Image
def run_detector(detector, path,img_file):
img = Image.open(path+img_file)
if img.mode == 'RGBA' :
img = img.convert('RGB')
converted_img = img.copy()
converted_img = converted_img.resize((227,227),Image.LANCZOS)
converted_img = np.array(converted_img, dtype=np.float32)
converted_img = converted_img / 255.
converted_img = converted_img.reshape([1,227,227,3])
converted_img = tf.constant(converted_img)
t1 = time.time()
result = detector(converted_img)
t2 = time.time()
print(f'検出時間 : {t2-t1:.3f} 秒' )
r = {key:value.numpy() for key,value in result.items()}
boxes = r['detection_boxes']
scores = r['detection_scores']
decode = np.frompyfunc( lambda p : p.decode('ascii'), 1, 1)
class_names = decode( r['detection_class_entities'] )
print(f'検出オブジェクト' )
n = np.count_nonzero(scores >= 0.25 )
for i in range(n):
y1, x1, y2, x2 = tuple(boxes[i])
x1, x2 = int(x1*img.width), int(x2*img.width)
y1, y2 = int(y1*img.height),int(y2*img.height)
t = f'{class_names[i]:10} {100*scores[i]:3.0f}% '
t += f'({x1:>4},{y1:>4}) - ({x2:>4},{y2:>4})'
print(t)
if "Cat" in t:
showImage(np.array(img), r, img_file,min_score=0.25)
return t2-t1
今回は、特に猫を検出した場合に出力するようにしたいので、”Cat”のクラスが検出された場合に画像が出力されるようにしました。
結果
今回の結果として、Faster R-CNNで行った結果は、100枚中73枚検出して出力を行っていました。両方で検知できた例についてご紹介します。
SSDとFaster R-CNNを比較すると、検出にかかった時間はSSDが平均0.23秒、Faster R-CNNが平均1.30秒でした。
また、SSDの方は74枚という結果でした。枚数は近いのですが、「猫」と検出した画像で重複していないものが意外と多く、検出方法による画像の得意・不得意があるということがよくわかったと思います。
どちらの結果も、猫以外の画像がほとんど含まれていなかったので、猫の画像のみを拾ってくるという点では成功したと言えるでしょう。猫ではないのに取得してしまった例として、「一覧で見た時は猫かと思ったが、よく見ると犬」という画像もありました。
また、「猫」と検出した画像の中でも珍しいのが、「絵で描かれた猫」を検出しているものでした。絵の猫でも検出できるというのはなかなか面白いと思いましたが、絵の猫と本物の猫の判別となると、そこでの学習が必要になるので、クラスの設定も難しそうです。
まとめ
猫の画像を検出して、それ以外の画像を拾わないようにすることができました。
しかし、それぞれの検出法では漏れがあることがわかったので、今後は両方を併用しての取得や、今流行りのDETRやYOLOv5を利用しての物体検出を実装して割り出すものや、セマンティックセグメンテーションで画像中の猫の部分のみ抽出できるようなシステムづくりにも挑戦してみたいと思います。
最後までお付き合いいただき、ありがとうございました!
参考にしたサイト
https://qiita.com/code0327/items/3b23fd5002b373dc8ae8
https://github.com/arc298/instagram-scraper
https://githubja.com/rarcega/instagram-scraper
▼この記事はQiitaでも公開しています。
https://qiita.com/ATcat/items/1588a42ba57f522b682f