コミケのカタログで遊ぶの巻 その1 OpenCV編

序文

皆さん、コミケから帰ったら紙のカタログは捨てるんでしょうか?
それ以前に冊子版よりもDVD-ROM版なんでしょうか?
「捨てるだけでは勿体無くないか?」
って事で、ちょっとした遊びを考えてみました。
と言いつつ、私もすぐに捨ててますけどね。カタログ。

そんなこんなで、
「捨てるであろうコミケカタログをネタに、パソコンで遊ぼうじゃないか!」
って企画です。

で、今回は・・・「OpenCVでサークルの画像を切り出そう!」です。

目的

今回はコミケの紙カタログからサークル画像を抜き出します。
コミケの紙カタログで無くとも、プロ野球、Jリーグ、NFL、Vリーグ、TVスター名鑑でも適当に、手短にある興味のある物でOKかと思います。
駿河屋の生写真福袋でも、トレカでも、安く入手出来れば何でもOKかと思います。
向き不向きはあると思いますが、結局、自分の興味あるブツでやるのが、一番よろしいかと。

ちなみに私がコミケのカタログを使用した理由は、
1)サークル画像が小さい
2)モノクロ画像
です。
データが大き過ぎたり、処理が重すぎるのも、自宅パソコンでは困りものなので。

裏目的

「コミケのサークル画像で機械学習とかディープラーニングの勉強できれば面白くないか?」
って言う、不純な考え。

その昔、ポ○モンをクラスタリングしてる人を見たことあります。
ネットか同人だったかは忘れた。
別に役立つ必要も、正しい正しくないとか、そんな事はどうでもいいんです。
遊べて、ちょっと学べればOK。

ちょっと真面目な話だと、一般書籍ではPCメモリの範囲内で処理できる事例が多いです。
Out of Coreな場合も試してみたいってのがあって、そういう意味ではコミケのカタログは手短にあるデータとして最適だった。

事前準備

まずは今回はコミケのカタログを使用するので、コミケのカタログが必要。
駿〇屋で売ってると思いきや、過去のカタログって売ってないなぁ。
しょうがないので、人から貰うなり、どっかから入手してください。

で、裁断してドキュメントスキャナで読み取るワケですが、なるべくスキャニングサイズが近い方が良かろうって事で、裁断せずに、背の糊から1ページずつ剥がしました。

面倒そうですが、これが一番確実な感じでした。
裁断機やディスクカッターとか、ちょっと遊ぶにゃ痛い出費です。

で、分断されたページをドキュメントスキャナで読み込む。
PDFではなく画像ファイル(PNG)で保存。
グレースケールでページ単位にしました。

「お前、さっき裁断機の出費が痛いとか言っといて、ドキュメントスキャナのが出費がデカいだろ!」
って指摘はごもっとも。
ただ、私は持ってた。申し訳ない。

スキャン結果ですが、裁断するより一枚づつ分離させた方がスキューが抑えられるような気がします。

ソフトウェアのインストール

開発言語はPython
お約束のNumpy
画像処理はOpenCV
概ねこんな感じ
後は各々の事情に合わせて追加すればOK

Pythonはpython.orgからダウンロードしてインストール
OpenCVとかは以下みたいな感じで

pip install opencv-python
pip install numpy

Anacondaをインストールしましょうとか書いてある書籍が多いと思うんですが、私は使った事がありません。
普通に秀丸エディタとかVisual Studio Codeで書いてます。

画像ファイルを開く

画像ファイルの読み込む。
カラーじゃなくて、グレースケールにする。
cv2.imreadの第二引数でcv2.IMREAD_GRAYSCALEを指定できるんですが、cv2.cvtColorのが安全だそうです。
よーしらん。

RGBをグレイスケールにするには、 DASK-IMAGE
(https://examples.dask.org/applications/image-processing.html)
を見ると、

(r * 0.2125) + (g * 0.7154) + (b * 0.0721)

ってことらしいですが、今回は前者で処理してます。

im = cv2.imread(file)
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)

傾き補正

ドキュメントスキャナで補正が掛かってても、微妙に傾いてる事があるので傾き補正する必要あり
コミケカタログのページは概ねこんな感じ
6 x 6 = 36サークルの紹介が1ページでされている

もっとも外側の枠を認識して回転させる事で傾きを補正させる
但し、一番外側は余白を含めた画像サイズ内側なので、2番目に大きな外枠を狙う事になる。

まず2値化する。
閾値は適当に
物体は黒でなくて白で認識するようです

ret,thresh = cv2.threshold(imggray,127,255,0)

次に輪郭検出
前段でCanny法でエッジ検出をしてる例もありましたが、結局、やりませんでした。

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

素の画像サイズ検出

h, w, c = im.shape

次に外枠線を取得。
枠線を認識するので、画像サイズからxOffset,yOffsetで、画像そのものの矩形認識を除外させました。
出力された画像を見て枠線が認識されているかチェックし、駄目だったらxOffset,yOffsetで調整します。
ちなみにソートして2番目を取得する方法もあるっぽいですが、そもそもどういう輪郭検出をしてくれるか分かりませんでしたので、いい時間、データと睨めっこする為に、かような方法としました。

for i in range(len(contours)):
    if cv2.contourArea(contours[i]) > max_area and cv2.contourArea(contours[i]) < float(h*w-xOffset*yOffset):
        max_area = cv2.contourArea(contours[i])
        max_area_index = i

次に矩形近似。
このままだと輪郭抽出したままなので多角形で認識されてる。
四角形で近似したいので、ざっくり端折る。

epsilon = 0.1*cv2.arcLength(contours[max_area_index],True)
approx = cv2.approxPolyDP(contours[max_area_index],epsilon,True)
imbox = cv2.drawContours(imbox,contours,max_area_index,(0,255,0),3)

次に矩形頂点認識。
矩形近似した座標から右上、右下、左上、左下を認識する。
一定の順番で出てくるワケじゃないそうです。
ベクトルの大きさでこの4点を認識させてます。
これもネットを眺めるといろいろとやり方があるっぽいです。
行列とか変えてますが、前段は端折って記載してます。

dAxApStrIdx = np.argsort(np.linalg.norm(dAxAp,axis=1))
lt = dAxAp[dAxApStrIdx[0]]
rb = dAxAp[dAxApStrIdx[3]]
rt = dAxAp[dAxApStrIdx[1]] if dAxAp[dAxApStrIdx[1]][0] > dAxAp[dAxApStrIdx[2]][0] else dAxAp[dAxApStrIdx[2]]
lb = dAxAp[dAxApStrIdx[1]] if dAxAp[dAxApStrIdx[1]][0] < dAxAp[dAxApStrIdx[2]][0] else dAxAp[dAxApStrIdx[2]]

傾きを回転で補正します。
面倒だったので、矩形の左上、右上の座標から回転角を得る。
通常、1度も曲がってないで1度以上は除外しました。
ドキュメントスキャナでありがたくない回転を勝手にしてくれる場合があるので、補正処理しないまま画像保存のが楽かもです。

if abs(rot) < 1:
	imrot = ndimage.rotate(imrot,rot)

これで傾きが修正できました。

画像抽出

サークル画像を切り出すとして場所が問題です 。
単純にサークルの四角を切り出せば良さそうですが、黒べたのサークルとかもあるので、うまく矩形認識してくれません。
そこで、番号の書かれた左肩の正方形を認識させてやります。
ここは黒べたの白抜き文字などは無いようです。
やってる事は前段と同じなので端折ります。

1)回転修正画像を再度矩形認識
2)面積取得&左肩四角認識
  左肩正方形の面積に近いものを抽出します
3)矩形近似と四角判定
4)画像切り出し

area = cv2.contourArea(contours[i])
if area > minASize and area < maxASize:
	if len(approx) == 4:
		(x,y) = lt.tolist()
		im = imrot[y:y+yBox,x:x+xBox]
		cv2.imwrite(SAVEDIR + filename + ".png",im)

結果

こんな感じ

その他

最後の画像切り取り時に、矩形認識のまま切ってしまえば良さそうですが、この後、機械学習などで使用する場合、同じ画像サイズのが処理が楽だし、元データをこねくり回さないので、そちらのがエエんじゃないかって判断です。

実際、固定サイズのまま切った結果を見ると、曲がった状態で切られてるモノはありませんでした。
なので、まあエエかと。

あと、自宅パソコンの能力を鑑みるに、150DPIでスキャニングすれば良かった気もしますが、300DPIで読み取ってしまい、やり直す気がなくて、そのままです。

サンプルのコードは、適当に元のコードから切り取って、変数名を変えたりしてるので、適時お好きなように変更してください。

次回

これを使用して教師なしクラスタリングと思いましたが、サークル名等との紐付などがないので、まずはサークルリストをOCR処理しようかと思ってます。
TESSERACT-OCRで文字認識なんで、ほぼほぼ定番。