[PYTHON] Détecter les post-it à partir d'images du tableau blanc

Bonsoir. Calendrier de l'Avent du groupe mixi 2015 C'est l'article du 14ème jour! ~~ En fait, je me suis également inscrit au Diverse Advent Calendar 2015. ~~ Il semblait que l'enregistrement en double n'était pas possible m (_ _) m Je vous remercie.

Contexte

Du coup, utilisez-vous tous des tableaux blancs? Dans le Groupe Mixy, certains départements ont intégré Scrum dans leurs méthodes de développement, et dans de nombreux cas Whiteboard + Post-it participe à la gestion des tâches. Il peut être désagréable pour les entreprises informatiques de l'ère informatique d'utiliser des méthodes analogiques pour la gestion des tâches, bien que partiellement, mais il est important de faire bon usage des avantages du numérique et de l'analogique.

Au fait, je suis aussi une personne qui travaille pour une telle entreprise informatique, donc naturellement j'ai un tableau blanc à la maison (↓ figure du tableau blanc à la maison). 我が家のホワイトボードの図 (C'était sale alors j'ai fait de mon mieux pour le nettoyer)

C'est très agréable de pouvoir se réveiller tous les matins et vérifier différentes choses en même temps en écrivant et en collant la tâche sur Post-it, mais quand je sors, j'ai tendance à oublier quelle était la tâche.

Cette fois, afin de réaliser l'envie de "vérifier le tableau blanc même quand je suis absent!", J'ai essayé de prendre une photo du tableau blanc et de gérer le Post-it numériquement, donc je vais vous présenter le chemin. (S'il y a un meilleur moyen! J'apprécierais que vous le signaliez!)

Environnement: Python 3.4.3 (pyenv), OpenCV 3.0.0

couler

--Installation d'OpenCV --Détection de contour à partir de la binarisation de l'image

Installation d'OpenCV

Dans l'environnement pyenv, vous pouvez facilement l'installer si Anaconda est installé. conda install -c https://conda.binstar.org/menpo opencv

Cette fois, je voulais l'utiliser sur l'environnement existant avec pyenv, donc je l'ai installé avec homebrew.

brew tap homebrew/science
brew install opencv3 --with-python3
brew link opencv3 --force

Vous devez créer un lien pour rendre OpenCV disponible à partir de Python 3 dans l'environnement pyenv.

ln -s /usr/local/Cellar/opencv3/3.0.0/lib/python3.4/site-packages/cv2.so ~/[pyenv_path]/versions/3.4.3/lib/python3.4/site-packages/cv2.so 

Si vous pouvez importer cv2 dans un environnement d'interprétation tel qu'ipython, vous avez terminé.

$ ipython
In [1]: import cv2
In [2]: cv2.__version__
Out[2]: '3.0.0'

Détection de contour à partir de la binarisation d'image

Comme l'image est binarisée à l'aide d'OpenCV et que seule la partie post-it en est extraite, la partie contour du post-it est acquise. Cette fois, j'ai utilisé cette photo de mon tableau blanc!

Example

Tout d'abord, la partie jusqu'à la binarisation.

image_dir = './image/'
image_file = 'xxx.jpg'
im = cv2.imread(image_dir + image_file, 1) #(A)
im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) #(B)
im_blur = cv2.GaussianBlur(im_gray, (11, 11), 0) #(C)

L'image est lue dans 3 canaux de couleur en (A), mais il est nécessaire de la convertir en une image en échelle de gris afin d'effectuer la binarisation, et cela se fait en (B). Le deuxième argument de (B) est une constante, et diverses autres conversions telles que BGR2HSV peuvent être effectuées avec cvtColor. Le flou gaussien est appliqué dans (C) pour faciliter la détermination du seuil de l'image en niveaux de gris.

L'image qui a été prétraitée est binarisée. La binarisation remplace une image donnée en échelle de gris par (essentiellement) deux couleurs, 0 et 255. La fonction de seuil est utilisée ici, mais il existe la fonction de seuil qui définit le seuil pour l'ensemble et la fonction adaptiveThreshold qui définit le seuil de manière adaptative en fonction de la pièce.

ret1, th1 = cv2.threshold(im_blur, 127, 255, cv2.THRESH_BINARY_INV)
th2 = cv2.adaptiveThreshold(im_blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 3)

seuil utilise le deuxième argument comme seuil et binarise l'image entière. Le 5ème argument de adaptiveThreshold est la plage à voir lors de la prise du seuil, et le seuil final est le seuil calculé à partir de cette plage moins le 6ème argument. Il existe deux façons de calculer le seuil dans une plage: cv2.ADAPTIVE_THRESH_MEAN_C, qui fait simplement la moyenne des pixels de la plage, et cv2.ADAPTIVE_THRESH_GAUSSIAN_C, qui pondère et calcule la moyenne gaussienne.

Le 4ème argument de seuil et le 3ème argument de adaptiveThreshold prennent diverses autres constantes. Veuillez consulter la liste de références pour plus de détails.

La figure ci-dessous montre l'application sur une image appropriée.

example_threshold

Puisque je veux détecter la partie post-it cette fois, j'ai utilisé l'algorithme OTSU, qui est un algorithme qui détermine bien le seuil, comme seuil qui détermine le seuil dans son ensemble. C'est très facile à utiliser, il suffit d'ajouter un peu à la fonction précédente.

th = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]

En termes simples, l'algorithme OTSU (algorithme Otsu) n'est pas particulièrement difficile et est généralement appelé méthode d'analyse discriminante, discriminant linéaire de Fisher, etc. Lorsqu'il y a deux classes (ici, l'arrière-plan de l'image et de l'objet, etc.), (1) la variance au sein de chaque classe est petite, et (2) la variance entre les classes est grande, ces (1) et (2) Nous recherchons un seuil qui atteindra. Par conséquent, il n'est pas nécessaire de définir un seuil par personnes, et si vous ne passez que l'image, le seuil optimal sera calculé automatiquement. Si vous souhaitez en savoir plus, veuillez vérifier avec "méthode d'analyse de discrimination".

En appliquant ceci à la première photo que j'ai soulevée, cela ressemble à ceci:

example_try

** Je ne peux pas du tout l'obtenir! !! !! !! !! ** ** Il semble juste que la saleté des lettres ressorte. Je ne pouvais pas l'obtenir simplement en créant une échelle de gris, alors cette fois j'ai utilisé la somme des cinq images binarisées suivantes comme image binarisée finale.

  1. Image originale binarisée avec cvtColor
  2. Une image en niveaux de gris unidimensionnelle des valeurs de chacun des trois canaux de couleur d'origine
  3. La somme du rouge des trois canaux de couleur moins les autres bleu et vert (créé pour supprimer le rouge de force car il était difficile à supprimer, im comme l'image d'origine, les valeurs suivantes)
(np.abs(int_im[:,:,2] - int_im[:,:,1]) + np.abs(int_im[:,:,2] - int_im[:,:,0]))

Voici 5 types d'images binarisées créées à partir de celles-ci, et la somme de toutes les images créées!

example_success

Ça devient comme ça.

Ensuite, je veux obtenir les coordonnées du contour, mais comme OpenCV a déjà une fonction findContours pour extraire le contour, je vais l'utiliser.

contours = cv2.findContours(im_th, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[1]

Cela permet d'obtenir des informations sur la pièce de contour.

Obtenez la partie post-it

Après cela, sur la base du sommet du contour acquis, seule la partie dont la surface est plus grande qu'une certaine taille est extraite, une approximation linéaire est effectuée par la fonction approxPolyDP et la partie carrée obtenue est découpée à partir de l'image d'origine et enregistrée.

# filtered with area over (all area / 100 )
th_area = im.shape[0] * im.shape[1] / 100
contours_large = list(filter(lambda c:cv2.contourArea(c) > th_area, contours))

outputs = []
rects = []
approxes = []

for (i,cnt) in enumerate(contours_large):
	arclen = cv2.arcLength(cnt, True)
	approx = cv2.approxPolyDP(cnt, 0.02*arclen, True)
	if len(approx) < 4:
		continue
	approxes.append(approx)
	rect = getRectByPoints(approx)
	rects.append(rect)
	outputs.append(getPartImageByRect(rect))
	cv2.imwrite('./out/output'+str(i)+'.jpg', getPartImageByRect(rect))

Ici, comme getPartImageByRect et getPartImageByRect, nous avons préparé une fonction pour obtenir l'image obtenue en découpant la plage spécifiée de l'image suivante.

def getRectByPoints(points):
	# prepare simple array 
	points = list(map(lambda x: x[0], points))

	points = sorted(points, key=lambda x:x[1])
	top_points = sorted(points[:2], key=lambda x:x[0])
	bottom_points = sorted(points[2:4], key=lambda x:x[0])
	points = top_points + bottom_points
	
	left = min(points[0][0], points[2][0])
	right = max(points[1][0], points[3][0])
	top = min(points[0][1], points[1][1])
	bottom = max(points[2][1], points[3][1])
	return (top, bottom, left, right)

def getPartImageByRect(rect):
	img = cv2.imread(image_dir + image_file, 1)
	return img[rect[0]:rect[1], rect[2]:rect[3]]

Vous pouvez maintenant découper la partie post-it de l'image d'origine. L'image suivante est celle sur laquelle j'ai mis un cadre dans l'image d'origine.

元画像with黒枠

Tous les post-it peuvent être détectés en toute sécurité. Après cela, si vous avez chaque image dans l'application ou si vous la gérez sur le serveur, vous pouvez ouvrir la voie à la gestion numérique.

Regroupement avec des couleurs post-it

Là où Post-it est utilisé, je pense que vous catégorisez souvent vos tâches par couleur. C'est un gros problème, alors gérons cela aussi. Considérez le flux suivant.

  1. Acquisition d'une couleur représentative
  2. Clustering
  3. Cadrez la couleur moyenne du même cluster que la couleur de cette catégorie

Tout d'abord, obtenez les couleurs représentatives de chaque image recadrée. Actuellement, seule la partie post-it est découpée, donc on pense que cette image contient trois couleurs: le blanc environnant, la couleur post-it et la couleur du texte noir. En d'autres termes, si la valeur médiane de chaque canal de couleur est simplement utilisée comme couleur représentative, la couleur Post-it est susceptible d'être obtenue.

t_colors = []
for (i,out) in enumerate(outputs):
	color = np.zeros(3)
	for j in range(3):
		color[j] = np.median(out[:,:,j])
	t_colors.append(color)
t_colors = np.array(t_colors)

Ensuite, les valeurs représentatives de chacun des post-it obtenus ont été regroupées en utilisant la méthode KMeans. En ce qui concerne le clustering par la méthode KMeans, il existe de nombreuses explications faciles à comprendre sur le net telles que Qiita, alors veuillez vous y référer.

from sklearn.cluster import KMeans
# KMeans
cluster_num = 4 # num of colors
kmeans = KMeans(n_clusters=cluster_num).fit(t_colors)
labels = kmeans.labels_
centers = np.array(kmeans.cluster_centers_).astype(np.int) # convert into int to express color

Maintenant, voici la couleur (et le numéro) du résultat groupé ajouté à l'image d'origine! C'est assez difficile à voir, mais j'ai ajouté une étiquette de résultat de clustering 0-3 dans le coin supérieur gauche de Postit.

clustering

En regardant les résultats, le bleu et le rouge sont bien regroupés, mais la classe est un mélange de jaune et de jaune-vert, probablement à cause de la lumière. Cela peut changer à nouveau si vous convertissez en HSV et incluez des informations de saturation car nous n'avons utilisé que les éléments de chaque canal RVB de l'image pour le clustering, mais c'est tout pour cette fois.

à partir de maintenant

Dans cet exemple, je n'ai confirmé le fonctionnement de mon tableau blanc qu'à la maison, je dois donc le rendre plus largement utilisable. De plus, le regroupement est également insuffisant et, à l'heure actuelle, il n'est pas possible de réagir lorsque le nombre de couleurs augmente. De plus, il semble insensé de prendre des photos tous les matins par moi-même, alors j'aimerais tirer le fier Raspberry Pi et tout automatiser, alors je vais l'écrire à nouveau.

Résumé

Quand j'essayais d'écrire un article sur l'introduction des tableaux blancs à la maison, je ne pouvais pas vraiment y toucher. Introduisons un tableau blanc à la maison. Vous pouvez gérer les tâches, calculer et rédiger des notes. Vous pouvez faire beaucoup de progrès.

Demain, @isaoshimizu écrira quelque chose. Je vous remercie.

référence

Créez un environnement pour Python 3.4 + OpenCV 3.0 sur votre Mac https://librabuch.jp/2015/07/python-34_opencv-30_mac/

Extraction de contour avec OpenCV http://docs.opencv.org/master/d4/d73/tutorial_py_contours_begin.html#gsc.tab=0

cv2.threshold (OpenCV 2.1) http://opencv.jp/opencv-2.1/cpp/miscellaneous_image_transformations.html#cv-threshold

cv2.adaptiveThreshold (OpenCV 2.1) http://opencv.jp/opencv-2.1/cpp/miscellaneous_image_transformations.html#cv-adaptivethreshold

cv2.findContours (OpenCV 2.1) http://opencv.jp/opencv-2.1/cpp/structural_analysis_and_shape_descriptors.html#cv-findcontours

Recommended Posts

Détecter les post-it à partir d'images du tableau blanc
Grattage immédiat des images google!