七転八転

よしなしごとを。

写真からの付箋検出について

前置き

この記事は圧倒的令和ッ!!ぴょこりんクラスタ Advent Calendar 2019 - Adventarのために書かれたものです。ちなみにこのACが何なのかについては、ぴょこりんクラスタ Advent Calendar is 何? - ぴょこりんブログが詳しいです。

前提

紙の付箋に書いたものが、自動的に電子化されて処理がなされたりしたら、ちょっと楽しいかもという思いつきがありました。電子で完結させろよという話もありますが、ちょっとしたメモは紙の方が楽なような。ということで、写真にうつった付箋を検出することにトライしてみました。

ぶっちゃけこのようなアプリがあり、

Post-it®

Post-it®

  • 3M Company
  • 仕事効率化
  • 無料
apps.apple.com
例えば写真からの付箋の認識→切り出して電子化→trelloへの投げ込みのようなことはやってくれるので、ゼロベースで作るのもどうかなという思いはありますが、画像認識系の練習をかねて。

データセット

このような写真を使いました。
f:id:darumap:20191207184624j:plain:w500

やったこと1(グレースケールからの画像二値化)

labs.eecs.tottori-u.ac.jp
上記のチュートリアル等を眺めると、画像を二値化してcv2.findContours()を使えばよさそうだと思う。

なので、グレースケール→二値化という方針でトライしてみる。

#!/usr/bin/python
# -*- Coding: utf-8 -*-

import numpy as np
import cv2

TARGET_DIR = "./"
TARGET_PATH = "sample1.jpg"

im_sample = cv2.imread(TARGET_DIR + TARGET_PATH)
im_gray = cv2.cvtColor(im_sample,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(im_gray,180,255,0)
#180は閾値です。べた書きですみません・・・

で2値化した画像を表示すると。。。
f:id:darumap:20191207191409j:plain:w500
あっ。。。

グレースケール化してしまうと、背景と付箋の分離が困難なようです。

やったこと2(色情報に基づく画像二値化)

という反省をいかし、付箋の色情報を分離に使うようにします。色情報にはRGBなどいくつかの表現方法がありますが、今回はHSVを使います。なぜこれが便利かというと、白~黒の無彩色について彩度(S)が0になるという特徴があるからです。色相(H)は使わなくてもよかったかもしれませんが、適当に閾値を設定してしまいました。彩度の低い領域を白に飛ばすようにします。

#Threshold Paramters
#For binarization
TH_H = 20
TH_S = 20

TARGET_DIR = "./"
TARGET_PATH = "sample1.jpg"

im_sample = cv2.imread(TARGET_DIR + TARGET_PATH)
im_hsv = cv2.cvtColor(im_sample,cv2.COLOR_BGR2HSV)

thresh = cv2.inRange(im_hsv, (0,0,0),(TH_H,TH_S,255))
thresh_not = cv2.bitwise_not(thresh)

で2値化した画像を表示すると。。。
f:id:darumap:20191207185658j:plain:w500
壁の模様が結構厳しいですが、付箋はきれいに黒になっていることがわかります。

やったこと3(二値画像からの輪郭検出)

二値画像から、輪郭を検出します。

contours, hierarchy = cv2.findContours(thresh_not,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

#filter Contours
area_pic = im_sample.shape[0]*im_sample.shape[1]
threshold_cont_min = area_pic/200

contours = list(filter(lambda x: cv2.contourArea(x) > threshold_cont_min , contours))
im_cont = im_sample.copy()

im_cont = cv2.drawContours(im_cont, contours,-1,(0,255,0),3)

rect_num = 0
for i in range(len(contours)):
	x,y,w,h = cv2.boundingRect(contours[i])
	cv2.rectangle(im_sample,(x,y),(x+w,y+h),(0,255,0),2)
	rect_num = rect_num + 1;
	cv2.imwrite(TARGET_DIR + "cut/" + str(rect_num) + ".jpg", im_sample[y:y+h,x:x+w])

findContoursの第二引数は輪郭同士の階層構造を保持するかで、今回は一番外側のみひろうようにしました。
で、輪郭内の領域の広さでフィルタをかける。
フィルタをかける前だと、
f:id:darumap:20191207185333j:plain:w500
こんな感じで、かけた後は
f:id:darumap:20191207185358j:plain:w500
でした。いくつか壁の模様で誤爆していてつらい。。。(面積での閾値でははじけなかった)

で外接する矩形を求めて、その領域を切り出す。
f:id:darumap:20191207184648j:plain:w500
何個か誤爆してますが、付箋の切り出しには成功しています。

終わりに

リハーサル(とは????)では、きれいな白背景だったが、本番画像の難易度が高かった。パラメータチューニングなしでロバストに検出するの、難しいんだろうな。。。
切った付箋はOCRなり等をかけて、自動でTodo化とかできたらいいな。