#プロローグ
男「えっ」
女「えっ」
男「なんで」
女「死神の目って寿命の半分と引き換えじゃない」
女「でも冷静に考えて寿命の半分は大きすぎるから、」
女「他のプランはないか、死神に聞いたの」
男「他のプラン」
女「そしたら、寿命5年と引き換えに、目をcloud visionにしてくれるお買い得プランがあるってきいて」
女「それにしちゃったの」
男「お買い得プラン」
男(くそ・・・困った・・・)
男(cloud visionだったら、__比較的安価で学習の必要がなく、文字起こしから顔認識・ランドマーク検出・ロゴ検出など、たった一つの画像から幅広い情報の取得__しかできないじゃないか・・・!)
男(仕方がない、念のためどれぐらい使えるものか調べておくか・・・)
男「そうか・・・それは仕方ないね。」
男「せっかくだから君の目(cloud vision)がどれぐらい使えるものか調べたい。」
男「君の死神の目(cloud vision)は、どれぐらい顔が見えればいいんだい」
#画像準備
女「日がそういうと思ってね、」
女「あらかじめ300枚のjpegを用意して検証してみたの」
男「300枚のjpeg」
女「写真はこんな感じ」
一枚目:Pana KutlumpasisによるPixabayからの画像
二枚目:Jerzy GóreckiによるPixabayからの画像
三枚目:Jerzy GóreckiによるPixabayからの画像
男「なるほど」
男「君の目(cloud vision)でこれを見たとき、海砂子にはどう見えているんだい?」
女「死神の目みたいに、名前が上に浮き上がるとかじゃなくてね」
女「JSON」
男「JSON」
女「こんな感じよ」
{'responses': [{'faceAnnotations': [{'boundingPoly': {'vertices': [{'y': 63},
{'x': 479, 'y': 63},
{'x': 479, 'y': 641},
{'y': 641}]},
'fdBoundingPoly': {'vertices': [{'x': 18, 'y': 152},
{'x': 429, 'y': 152},
{'x': 429, 'y': 583},
{'x': 18, 'y': 583}]},
'landmarks': [{'type': 'LEFT_EYE',
'position': {'x': 148.54025, 'y': 326.71082, 'z': -0.00029802322}},
{'type': 'RIGHT_EYE',
'position': {'x': 303.61057, 'y': 334.2225, 'z': -5.350756}},
{'type': 'LEFT_OF_LEFT_EYEBROW',
'position': {'x': 99.92504, 'y': 287.5173, 'z': 16.986914}},
{'type': 'RIGHT_OF_LEFT_EYEBROW',
'position': {'x': 190.41669, 'y': 292.81528, 'z': -27.6378}},
{'type': 'LEFT_OF_RIGHT_EYEBROW',
'position': {'x': 262.69016, 'y': 299.08835, 'z': -30.459879}},
{'type': 'RIGHT_OF_RIGHT_EYEBROW',
'position': {'x': 351.62994, 'y': 297.73688, 'z': 8.217118}},
{'type': 'MIDPOINT_BETWEEN_EYES',
'position': {'x': 224.88486, 'y': 325.62115, 'z': -32.623844}},
{'type': 'NOSE_TIP',
'position': {'x': 224.316, 'y': 411.67432, 'z': -79.49573}},
{'type': 'UPPER_LIP',
'position': {'x': 220.57635, 'y': 469.11096, 'z': -49.109318}},
{'type': 'LOWER_LIP',
'position': {'x': 217.29926, 'y': 524.755, 'z': -41.423588}},
{'type': 'MOUTH_LEFT',
'position': {'x': 160.05147, 'y': 487.57834, 'z': -8.887493}},
{'type': 'MOUTH_RIGHT',
'position': {'x': 281.34888, 'y': 493.29626, 'z': -13.180865}},
{'type': 'MOUTH_CENTER',
'position': {'x': 220.49841, 'y': 490.98874, 'z': -39.32093}},
{'type': 'NOSE_BOTTOM_RIGHT',
'position': {'x': 261.48425, 'y': 430.9367, 'z': -27.783}},
{'type': 'NOSE_BOTTOM_LEFT',
'position': {'x': 186.51445, 'y': 425.8558, 'z': -24.476292}},
{'type': 'NOSE_BOTTOM_CENTER',
'position': {'x': 222.63399, 'y': 438.12354, 'z': -48.22904}},
{'type': 'LEFT_EYE_TOP_BOUNDARY',
'position': {'x': 147.68315, 'y': 312.52655, 'z': -7.486957}},
{'type': 'LEFT_EYE_RIGHT_CORNER',
'position': {'x': 180.05107, 'y': 330.44492, 'z': -1.2555599}},
{'type': 'LEFT_EYE_BOTTOM_BOUNDARY',
'position': {'x': 147.1899, 'y': 338.3902, 'z': -2.1412106}},
{'type': 'LEFT_EYE_LEFT_CORNER',
'position': {'x': 118.05368, 'y': 329.19965, 'z': 14.114943}},
{'type': 'RIGHT_EYE_TOP_BOUNDARY',
'position': {'x': 306.2247, 'y': 319.0188, 'z': -12.892803}},
{'type': 'RIGHT_EYE_RIGHT_CORNER',
'position': {'x': 333.90405, 'y': 338.98727, 'z': 6.6428103}},
{'type': 'RIGHT_EYE_BOTTOM_BOUNDARY',
'position': {'x': 304.06787, 'y': 346.5323, 'z': -7.610615}},
{'type': 'RIGHT_EYE_LEFT_CORNER',
'position': {'x': 270.48764, 'y': 334.90814, 'z': -4.4173594}},
{'type': 'LEFT_EYEBROW_UPPER_MIDPOINT',
'position': {'x': 143.69545, 'y': 277.00912, 'z': -13.745918}},
{'type': 'RIGHT_EYEBROW_UPPER_MIDPOINT',
'position': {'x': 308.14636, 'y': 285.0879, 'z': -19.769423}},
{'type': 'LEFT_EAR_TRAGION',
'position': {'x': 69.11742, 'y': 380.72174, 'z': 183.73578}},
{'type': 'RIGHT_EAR_TRAGION',
'position': {'x': 398.45352, 'y': 410.97098, 'z': 169.3166}},
{'type': 'FOREHEAD_GLABELLA',
'position': {'x': 226.25429, 'y': 294.6015, 'z': -34.059406}},
{'type': 'CHIN_GNATHION',
'position': {'x': 214.09215, 'y': 586.6705, 'z': -23.38647}},
{'type': 'CHIN_LEFT_GONION',
'position': {'x': 95.62633, 'y': 502.62048, 'z': 115.71633}},
{'type': 'CHIN_RIGHT_GONION',
'position': {'x': 360.29526, 'y': 514.63885, 'z': 105.16836}},
{'position': {'x': 118.65398, 'y': 426.6812, 'z': 9.878972}},
{'position': {'x': 327.72342, 'y': 436.22684, 'z': 2.6132486}}],
'rollAngle': 2.2244804,
'panAngle': -1.9717689,
'tiltAngle': 1.7747657,
'detectionConfidence': 0.73282397,
'landmarkingConfidence': 0.56908554,
'joyLikelihood': 'UNLIKELY',
'sorrowLikelihood': 'VERY_UNLIKELY',
'angerLikelihood': 'VERY_UNLIKELY',
'surpriseLikelihood': 'VERY_UNLIKELY',
'underExposedLikelihood': 'VERY_UNLIKELY',
'blurredLikelihood': 'VERY_UNLIKELY',
'headwearLikelihood': 'VERY_UNLIKELY'}]}]}
男「あれ、その人の名前分からないの」
女「そりゃあお買い得プランだもの見えるわけないじゃない」
男「えっっ」
女「えっっ」
#画像の加工
女「それでね」
女「ここに用意した画像を、端から一定の割合で黒塗りしていったの」
女「それがこれよ」
女「上から縦ピクセル数の5%刻みで黒塗りをしたものよ」
女「同じ要領で右から、左から、下から黒塗りしていったものを用意したわ」
女「5%刻みだから、それぞれ20枚ずつ画像ができるイメージね」
女「上下から黒塗りしたものと左右から黒塗りしたものも用意したわ」
女「これは黒塗り面積を10%ずつ増やすようにしたわ」
女「こんな感じよ」
女「1枚の写真で20枚×4方向+10枚×2方向で100枚」
女「それを3枚の画像に対して行うから300枚よ」
男「マメすぎる」
#検証
女「あとはこれを(cloud visionで)見るだけ」
女「まず上から黒塗りした結果はこれよ」
女「左がギリギリ認識できたもの、右がギリギリ認識できなかったものよ」
男「面白い」
男「鼻が入っているかどうかぐらいが基準なんだろうか」
女「他の結果はこんな感じよ」
女「これも左がギリギリ認識できたもの、右がギリギリ認識できなかったものだと思ってちょうだい」
男「なるほど」
男「右方向のものは鼻がほとんど隠れていても認識できてるみたいだが」
男「おおむね顔の半分ぐらいを目安と考えてよさそうだ」
女「この結果を受けて、もしかしたら鼻が大事なのかも?と思ったから」
女「上下から黒塗りしたもの、左右から黒塗りしたものもやってみたわ」
女「同じく、これも左がギリギリ認識できたもの、右がギリギリ認識できなかったものだと思ってちょうだい」
男「鼻の有無は関係ないようだね」
男「顔の面積が関係あるのかな?」
女「どうやらそのようね」
女「あと今回の結果を鑑みるに」
女「リファレンスには"両目の間の距離が最も重要"と書いてるみたいだけど」
女「これは両目が必須で入っていないといけないよ、という意味ではなく、正確に座標を取得するには必須だよ、という感覚みたいね」
男「リファレンス」
(参考:https://cloud.google.com/vision/docs/supported-files?hl=ja)
女「ちなみに両目が画像中にない時も、JSONには一応両目の座標情報も入ってくるみたい」
女「画像になくても、その座標を予測しにいってるみたいね」
女「この画像に対してこんな情報が得られるわ」
{'responses': [{'faceAnnotations': [{'boundingPoly': {'vertices': [{'x': 46,
'y': 191},
{'x': 402, 'y': 191},
{'x': 402, 'y': 604},
{'x': 46, 'y': 604}]},
'fdBoundingPoly': {'vertices': [{'x': 40, 'y': 156},
{'x': 411, 'y': 156},
{'x': 411, 'y': 581},
{'x': 40, 'y': 581}]},
'landmarks': [{'type': 'LEFT_EYE',
'position': {'x': 168.6482, 'y': 391.89807, 'z': -0.0013809204}},
{'type': 'RIGHT_EYE',
'position': {'x': 262.70505, 'y': 404.6311, 'z': 4.639324}},
{'type': 'LEFT_OF_LEFT_EYEBROW',
'position': {'x': 135.0105, 'y': 368.2699, 'z': 11.827677}},
{'type': 'RIGHT_OF_LEFT_EYEBROW',
'position': {'x': 190.1691, 'y': 375.63394, 'z': -13.081133}},
{'type': 'LEFT_OF_RIGHT_EYEBROW',
'position': {'x': 243.83943, 'y': 365.42114, 'z': -7.654064}},
{'type': 'RIGHT_OF_RIGHT_EYEBROW',
'position': {'x': 297.7882, 'y': 365.6126, 'z': 23.791716}},
{'type': 'MIDPOINT_BETWEEN_EYES',
'position': {'x': 222.57771, 'y': 375.29266, 'z': -13.268478}},
{'type': 'NOSE_TIP',
'position': {'x': 219.9367, 'y': 421.6402, 'z': -51.575165}},
{'type': 'UPPER_LIP',
'position': {'x': 218.8858, 'y': 471.7022, 'z': -41.076263}},
{'type': 'LOWER_LIP',
'position': {'x': 220.26483, 'y': 524.71875, 'z': -45.122192}},
{'type': 'MOUTH_LEFT',
'position': {'x': 163.0762, 'y': 487.3199, 'z': -19.719418}},
{'type': 'MOUTH_RIGHT',
'position': {'x': 274.22202, 'y': 493.15793, 'z': -14.107767}},
{'type': 'MOUTH_CENTER',
'position': {'x': 221.73895, 'y': 495.4584, 'z': -38.66844}},
{'type': 'NOSE_BOTTOM_RIGHT',
'position': {'x': 251.71823, 'y': 439.79745, 'z': -17.844486}},
{'type': 'NOSE_BOTTOM_LEFT',
'position': {'x': 191.96806, 'y': 440.76276, 'z': -21.761915}},
{'type': 'NOSE_BOTTOM_CENTER',
'position': {'x': 219.11682, 'y': 448.15125, 'z': -36.156685}},
{'type': 'LEFT_EYE_TOP_BOUNDARY',
'position': {'x': 164.96176, 'y': 383.65067, 'z': -3.757038}},
{'type': 'LEFT_EYE_RIGHT_CORNER',
'position': {'x': 192.45645, 'y': 394.0722, 'z': 0.99622726}},
{'type': 'LEFT_EYE_BOTTOM_BOUNDARY',
'position': {'x': 164.21245, 'y': 395.16943, 'z': -2.4103413}},
{'type': 'LEFT_EYE_LEFT_CORNER',
'position': {'x': 143.1513, 'y': 389.5244, 'z': 7.4084187}},
{'type': 'RIGHT_EYE_TOP_BOUNDARY',
'position': {'x': 265.0786, 'y': 394.31024, 'z': 0.98534393}},
{'type': 'RIGHT_EYE_RIGHT_CORNER',
'position': {'x': 283.98154, 'y': 401.04593, 'z': 15.598318}},
{'type': 'RIGHT_EYE_BOTTOM_BOUNDARY',
'position': {'x': 268.1015, 'y': 406.15326, 'z': 3.0626888}},
{'type': 'RIGHT_EYE_LEFT_CORNER',
'position': {'x': 247.38344, 'y': 397.30167, 'z': 4.236017}},
{'type': 'LEFT_EYEBROW_UPPER_MIDPOINT',
'position': {'x': 162.96875, 'y': 359.71533, 'z': -3.8579617}},
{'type': 'RIGHT_EYEBROW_UPPER_MIDPOINT',
'position': {'x': 272.22327, 'y': 354.1045, 'z': 4.445429}},
{'type': 'LEFT_EAR_TRAGION',
'position': {'x': 76.61041, 'y': 424.8057, 'z': 107.746376}},
{'type': 'RIGHT_EAR_TRAGION',
'position': {'x': 379.4829, 'y': 423.5393, 'z': 121.613625}},
{'type': 'FOREHEAD_GLABELLA',
'position': {'x': 223.69774, 'y': 350.03192, 'z': -9.278433}},
{'type': 'CHIN_GNATHION',
'position': {'x': 218.22043, 'y': 577.06055, 'z': -43.270943}},
{'type': 'CHIN_LEFT_GONION',
'position': {'x': 110.32744, 'y': 517.91125, 'z': 49.086758}},
{'type': 'CHIN_RIGHT_GONION',
'position': {'x': 329.2049, 'y': 529.6959, 'z': 59.23594}},
{'position': {'x': 140.9345, 'y': 456.33582, 'z': -6.282221}},
{'position': {'x': 286.94244, 'y': 467.3045, 'z': 1.9531765}}],
'rollAngle': 0.32991463,
'panAngle': 3.761013,
'tiltAngle': 11.58744,
'detectionConfidence': 0.31546676,
'landmarkingConfidence': 0.015264977,
'joyLikelihood': 'UNLIKELY',
'sorrowLikelihood': 'VERY_UNLIKELY',
'angerLikelihood': 'VERY_UNLIKELY',
'surpriseLikelihood': 'VERY_UNLIKELY',
'underExposedLikelihood': 'VERY_UNLIKELY',
'blurredLikelihood': 'VERY_UNLIKELY',
'headwearLikelihood': 'UNLIKELY'}]}]}
女「他の画像との比較も載せておくわ」
女「それぞれ、ギリギリ認識できなかったものを示すわ」
上方向 画像比較
下方向 画像比較
右方向 画像比較
左方向 画像比較
左右方向 画像比較
上下方向 画像比較
女「厳密に何かが隠れたら認識できなくなるとかはないようだけれど」
女「やはり表に出ている顔の面積が大事そうね」
#カラーと白黒
男「待ってくれ」
男「表に出ている顔の面積が大事だとして」
男「その面積ってどう認識してるんだろうか」
男「画像を白黒にしたら何も見えなくなるとかあるんじゃないか?」
女「あなたがそういうと思って、白黒の写真でも検証してみたの」
女「結果から言うと、認識精度はほとんど変わらなかったわ」
女「この二つについては認識精度が変わったけれど」
右方向(カラーのほうがやや精度良い)
左右方向(白黒のほうがやや精度良い)
女「他の4つについては精度が全く同じになったわ」
男「なるほど・・・」
#写真の大きさ
男「写真の大きさは?」
女「あなたがそういうと思って」
男「どれだけ予想していたんだ」
女「画像の大小を変えたものでも検証したの」
女「ここまでの写真が500×700だったんだけど」
女「半分の250×350の小さい画像と、2倍の1000×1400の大きい画像を用意したわ」
女「この結果が面白かったから共有するわね」
女「結果を並べるけど、見やすさの都合で画像の大きさは統一してるわ」
女「左が250×350、真ん中が500×700、右が1000×1400の結果よ」
上方向 画像大小比較
下方向 画像大小比較
右方向 画像大小比較
左方向 画像大小比較
左右方向 画像大小比較
上下方向 画像大小比較
男「縮小画像と拡大画像、500×700の画像と微妙に違くないか?」
女「ごめんなさい、こんな追加実験すると思わなかったから、」
女「真ん中の画像と他の二つは別のタイミングで用意したものなの」
女「一般性を保つためにあえて微妙に違う画像を用意したと思い込んで」
男「一般性」
男「結果を見る限り、あまり変わらないのか」
女「そうね、ほとんど変わらないみたい」
女「おそらく目(cloud vision)の内部で拡大縮小が行われてるんじゃないかしら」
女「だから、内部的にはこの3種類の画像はほぼ同じ画像として認識されているんじゃないかしら」
男「下方向・左右方向については、250×350・1000×1400のほうが500×700が精度が良い結果になっているのが気になるな」
女「さっき話のあった、画像を用意したタイミングによる違いの可能性も捨てきれないけれど」
女「一旦は誤差だと思っていいと思う」
#余白のある画像
男「待てよ」
男「内部的に拡大縮小が行われると仮定すると」
男「顔の部分の画像の大きさが同じで、周りに余計な余白がある画像はどうなるんだ?」
男「一定のサイズに拡大縮小されてしまうなら、顔部分の面積は相対的に小さくなってしまうはず」
女「あなたがそういうと思ってね」
男「ここまでくると怖いよ」
女「検証してみたの」
女「同じ画像に対して、周りに余白を付けた画像を用意したわ」
女「見やすさの都合でここでは縮小しているけど、左が今までと同じ500×700、真ん中が顔画像の周りに余白を付けた1000×1400、右が余白を増やした2000×2800よ」
女「余白を白で埋めてしまったから見にくくてごめんなさい」
女「日の仮説が正しいとすると、余白がおおい画像はcloud vision内で縮小されてしまうはずだから、精度が悪化すると考えられるわね」
女「検証結果を示すわ、また同様にぎりぎり認識できない画像を示すわね」
女「左が余白無し、真ん中が余白ありの1000×1400、右が余白ありの2000×2800の結果よ」
上方向 余白大小比較
下方向 余白大小比較
右方向 余白大小比較
左方向 余白大小比較
男「精度が悪くならない・・・?」
女「画像中にある顔写真の面積はどの写真も同じ、ということに留意して考えると、内部的に単純に拡大縮小をしているわけではなさそうね」
女「もしかしたらトリミングなどの応用的な処理もしているのかもしれない」
女「認識の精度も多少の差はあれど、あまり変わらないみたいね」
#エピローグ
女「今回の検証で分かることはここまでね、トリミングも可能性に入れると可能性があまりに多すぎるわ」
男「ありがとう、君の目(cloud vision)は(何かに)使えそうだ」
女「ありがとう。今後も引き続き検証するわね」
男(さあどうしよう・・・)
男(俺が目の取引するかあ・・・)
#サンプルコード
※実験内容に応じて多少コードの修正を行っています。例えばカラー・白黒によって画像データの配列構造が異なるため、一部微調整が必要になります。
##画像加工
import cv2, os
import matplotlib.pyplot as plt
img = cv2.imread("orig.jpg", 1)
#上から黒塗り
for i in range(0,20,1):
img[0:i*35] = 0
cv2.imwrite("up/"+str(i*5)+"per.jpg", img)
img = cv2.imread("orig.jpg", 1)
#下から黒塗り
for i in range(0,20,1):
img[700-i*35:700] = 0
cv2.imwrite("down/"+str(i*5)+"per.jpg", img)
#左から黒塗り
img = cv2.imread("orig.jpg", 1)
for i in range(0,20,1):
img.T[0][0:i*25] = 0
img.T[1][0:i*25] = 0
img.T[2][0:i*25] = 0
cv2.imwrite("left/"+str(i*5)+"per.jpg", img)
#右から黒塗り
img = cv2.imread("orig.jpg", 1)
for i in range(0,20,1):
img.T[0][500-i*25:500] = 0
img.T[1][500-i*25:500] = 0
img.T[2][500-i*25:500] = 0
cv2.imwrite("right/"+str(i*5)+"per.jpg", img)
#上下から黒塗り
img = cv2.imread("orig.jpg", 1)
for i in range(0,10,1):
img[0:i*35] = 0
img[700-i*35:700] = 0
cv2.imwrite("updown/"+str(i*10)+"per.jpg", img)
#左右から黒塗り
img = cv2.imread("orig.jpg", 1)
for i in range(0,10,1):
img.T[0][0:i*25] = 0
img.T[1][0:i*25] = 0
img.T[2][0:i*25] = 0
img.T[0][500-i*25:500] = 0
img.T[1][500-i*25:500] = 0
img.T[2][500-i*25:500] = 0
cv2.imwrite("leftright/"+str(i*10)+"per.jpg", img)
##cloud visionによる顔認識
※やや冗長ですが、自分の混乱防止のためなのでご容赦ください。
#cloud visionへPOST
import base64
import codecs
import json
import requests
import pandas as pd
def vision_api(image_content):
#基本パラメータ
GOOGLE_CLOUD_VISION_API_URL = 'https://vision.googleapis.com/v1/images:annotate?key='
API_KEY = (cloud visionのapiキー)
api_url = GOOGLE_CLOUD_VISION_API_URL + API_KEY
#画像データをエンコード
image_base64 = base64.b64encode(image_content.read()).decode()
#POSTするJSONの用意
req_body = json.dumps({
"requests": [{
"image":{
"content": image_base64
},
"features": [
{
"type": "FACE_DETECTION"
},
]
}]
})
res = requests.post(api_url, data=req_body)
return res.json()
#フォルダ名リスト
folder_list = ["up","down","left","right","updown","leftright"]
#一方向から黒塗りした結果の取得
all_result = []
#フォルダを一つずつ変えていく
for folder_idx in range(0,4,1):
folder = folder_list[folder_idx]
result_list = []
#画像をバイナリとしcloud visionへpost
for i in range(0,20,1):
img = open(folder+"/"+str(i*5)+"per.jpg","rb")
result = vision_api(img)
#取得したjsonに"faceAnnotations"があれば検出成功、なければ検出失敗
try:
result["responses"][0]["faceAnnotations"]
except KeyError:
result_list.append("失敗")
else:
result_list.append("成功")
#結果をリストに保存
all_result.append(result_list)
#二方向から黒塗りした結果の取得
all_result_2 = []
#フォルダを一つずつ変えていく
for folder_idx in range(4,6,1):
folder = folder_list[folder_idx]
result_list = []
#画像をバイナリとしcloud visionへpost
for i in range(0,10,1):
img = open(folder+"/"+str(i*10)+"per.jpg","rb")
result = vision_api(img)
#取得したjsonに"faceAnnotations"があれば検出成功、なければ検出失敗
try:
result["responses"][0]["faceAnnotations"]
except KeyError:
result_list.append("失敗")
else:
result_list.append("成功")
print(result_list)
#結果をリストに保存
all_result_2.append(result_list)
##取得結果の整理
df = pd.DataFrame(all_result,columns = range(0,100,5),index = folder_list[0:4])
df
df_2 = pd.DataFrame(all_result_2,columns = range(0,100,10),index = folder_list[4:6])
df_2
#さいごに
最後まで読んでいただきありがとうございました。
cloud vision周りを勉強しておりますので、アドバイスやご指摘等ありましたらご教示いただけると嬉しいです!
#参考
・PythonでGoogleのCloud Vision APIを利用して画像から日本語文字検出する
https://qiita.com/w2or3w/items/c7288e8ce62d061ff6d2
・WindowsでCP932(Shift-JIS)エンコード以外のファイルを開くのに苦労した話
https://qiita.com/Yuu94/items/9ffdfcb2c26d6b33792e