[PYTHON] Segmentation et regroupement de photos avec DBSCAN

introduction

Cet article fait partie de l'exemple de code pour Kaggle: The Nature Conservancy Fisheries Monitoring. Le programme lié à CNN n'est pas répertorié ici. Les détails seront résumés à une date ultérieure.

Ici, nous effectuerons deux analyses à partir des photographies du site de pêche.

  1. Estimez un identifiant unique pour chaque bateau
  2. Segmentez l'emplacement du poisson à partir de la photo

1. Regroupement des bateaux qui ont pris des photos

Ici, nous allons présenter Comment regrouper automatiquement les types de bateaux qui ont pris des photos. Par exemple, il peut être utilisé pour créer un modèle différent pour chaque bateau, ou pour masquer des zones où il n'y a pas de poisson sur le plateau.

Créez une fonction pour afficher les images côte à côte. Il existe deux types, quatre et huit. Après cela, 500 échantillons sont lus à partir du train.

import pandas as pd
import numpy as np
import glob
from sklearn import cluster
from scipy.misc import imread
import cv2
import skimage.measure as sm
# import progressbar
import multiprocessing
import random
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
new_style = {'grid': False}
plt.rc('axes', **new_style)

# Function to show 4 images
def show_four(imgs, title):
    #select_imgs = [np.random.choice(imgs) for _ in range(4)]
    select_imgs = [imgs[np.random.choice(len(imgs))] for _ in range(4)]
    _, ax = plt.subplots(1, 4, sharex='col', sharey='row', figsize=(20, 3))
    plt.suptitle(title, size=20)
    for i, img in enumerate(select_imgs):
        ax[i].imshow(img)

# Function to show 8 images
def show_eight(imgs, title):
    select_imgs = [imgs[np.random.choice(len(imgs))] for _ in range(8)]
    _, ax = plt.subplots(2, 4, sharex='col', sharey='row', figsize=(20, 6))
    plt.suptitle(title, size=20)
    for i, img in enumerate(select_imgs):
        ax[i // 4, i % 4].imshow(img)

select = 500 # Only load 500 images for speed
# Data loading
train_files = sorted(glob.glob('../input/train/*/*.jpg'), key=lambda x: random.random())[:select]
train = np.array([imread(img) for img in train_files])
print('Length of train {}'.format(len(train)))

La taille des images de train n'est pas unifiée. Existe-t-il une taille de photo qui ne comprend que certains bateaux? Vérifions l'ID du bateau comme taille d'image.

print('Sizes in train:')
shapes = np.array([str(img.shape) for img in train])
pd.Series(shapes).value_counts()
(720, 1280, 3)    287
(750, 1280, 3)     81
(974, 1280, 3)     50
(670, 1192, 3)     29
(718, 1276, 3)     28
(924, 1280, 3)      9
(974, 1732, 3)      7
(700, 1244, 3)      5
(854, 1518, 3)      3
(750, 1334, 3)      1
dtype: int64

Divisé la taille. Affiche quatre images réelles à la fois.

for uniq in pd.Series(shapes).unique():
    show_four(train[shapes == uniq], 'Images with shape: {}'.format(uniq))
    plt.show()

À l'exception de la taille d'image (854, 1518, 3), les autres images contiennent un ou plusieurs bateaux. Une autre approche sera probablement nécessaire pour considérer l'ID du bateau.

Regroupement de bateaux

Par souci de simplicité, nous analyserons ces 500 données. Bien entendu, le même traitement peut être effectué en utilisant toutes les données d'image. Analysez dans les trois étapes suivantes.

# Function for computing distance between images
def compare(args):
    img, img2 = args
    img = (img - img.mean()) / img.std()
    img2 = (img2 - img2.mean()) / img2.std()
    return np.mean(np.abs(img - img2))

# Resize the images to speed it up.
train = [cv2.resize(img, (224, 224), cv2.INTER_LINEAR) for img in train]

# Create the distance matrix in a multithreaded fashion
pool = multiprocessing.Pool(8)
#bar = progressbar.ProgressBar(max=len(train))
distances = np.zeros((len(train), len(train)))
for i, img in enumerate(train): #enumerate(bar(train)):
    all_imgs = [(img, f) for f in train]
    dists = pool.map(compare, all_imgs)
    distances[i, :] = dists

Créez une matrice NxN. N est le nombre d'images, et cette matrice montre la distance entre les images. SKLearn a de nombreuses méthodes de clustering qui peuvent utiliser des matrices de distance pré-calculées. Ici, la matrice de distance est donnée à DBSCAN pour le clustering.

print(distances)
plt.hist(distances.flatten(), bins=50)
plt.title('Histogram of distance matrix')
print('')

__results___9_1.png

Vous pouvez voir qu'il y a une aire de 0,8 ou moins. Probablement lors du calcul de la distance entre les images du même bateau. DBSCAN considère les distances jusqu'à 0,5 comme des clusters similaires. En regardant l'histogramme, nous jugeons que 0,6 est un seuil approprié.

cls = cluster.DBSCAN(metric='precomputed', min_samples=5, eps=0.6)
y = cls.fit_predict(distances)
print(y)
print('Cluster sizes:')
print(pd.Series(y).value_counts())

for uniq in pd.Series(y).value_counts().index:
    if uniq != -1:
        size = len(np.array(train)[y == uniq])
        if size > 10:
            show_eight(np.array(train)[y == uniq], 'BoatID: {} - Image count {}'.format(uniq, size))
            plt.show()
        else:
            show_four(np.array(train)[y == uniq], 'BoatID: {} - Image count {}'.format(uniq, size))
            plt.show()

Cela a plutôt bien fonctionné. Cependant, il y avait un groupe qui n'appartenait à aucune carte d'identité de bateau. Il y a deux raisons possibles.

  1. Il n'y a que 5 images ou moins définies comme seuil d'identification du bateau
  2. La fonction de distance n'est pas suffisante pour regrouper des images. Par exemple, les photos de jour et de nuit sont mélangées.
size = len(np.array(train)[y == -1])
show_eight(np.array(train)[y == -1], 'BoatID: {} (Unclassified images) - Image count {}'.format(-1, size))

Certains peuvent créer visuellement des clusters. En d'autres termes, il y a place à l'amélioration de l'algorithme. Cependant, le fait que plus de 75% des bateaux puissent être classés par apprentissage non supervisé signifie que le classement était assez précis.

2. Segmentation du poisson

Segmentez l'image du poisson. Comme procédure de travail

  1. Préparez une photo de poisson comme modèle
  2. Sélectionnez une photo autre que le modèle et sélectionnez la méthode de segmentation appropriée avec plusieurs algorithmes
  3. Segmentez plusieurs photos à l'aide de l'algorithme que vous avez sélectionné précédemment
import os 
from scipy import ndimage
from subprocess import check_output

import cv2
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline

img_rows, img_cols= 350, 425
im_array = cv2.imread('../input/train/LAG/img_00091.jpg',0)
template = np.zeros([ img_rows, img_cols], dtype='uint8') # initialisation of the template
template[:, :] = im_array[100:450,525:950] # I try multiple times to find the correct rectangle. 
#template /= 255.
plt.subplots(figsize=(10, 7))
plt.subplot(121),plt.imshow(template, cmap='gray') 
plt.subplot(122), plt.imshow(im_array, cmap='gray')

Utilisez des données différentes de la photo utilisée précédemment comme modèle. En utilisant matchTemplate d'opencv, vous pouvez trouver une partie similaire au modèle préparé. Il existe plusieurs types de méthodes optionnelles, nous allons donc expérimenter chacune des six méthodes. L'emplacement spécifié est défini pour être entouré d'un carré.

file_name = '../input/train/LAG/img_01512.jpg' # img_00176,img_02758, img_01512
img = cv2.imread(file_name,0) 
img2 = img
w, h = template.shape[::-1]

# All the 6 methods for comparison in a list
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
            'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']

for meth in methods:
     img = img2
     method = eval(meth)
 
     # Apply template Matching
     res = cv2.matchTemplate(img,template,method)
     min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
 
     # If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
     if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
         top_left = min_loc
     else:
         top_left = max_loc
     bottom_right = (top_left[0] + w, top_left[1] + h)
 
     cv2.rectangle(img,top_left, bottom_right, 255, 2)
     fig, ax = plt.subplots(figsize=(12, 7))
     plt.subplot(121),plt.imshow(res,cmap = 'gray')
     plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
     plt.subplot(122),plt.imshow(img,cmap = 'gray') #,aspect='auto'
     plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
     plt.suptitle(meth)
 
     plt.show()

Lorsque je l'exécute, je peux bien segmenter le poisson par des méthodes autres que TM_SQDIFF et TM_SQDIFF_NORMED. Par conséquent, cette fois, TM_CCOEFF est utilisé comme méthode de découverte.

Les photos de ce concours sont limitées à 8 types de poissons. Par conséquent, sélectionnez 4 photos de chaque espèce de poisson et exécutez la segmentation avec TM_CCOEFF.

method = eval('cv2.TM_CCOEFF')
indexes=[1,30,40,5]

train_path = "../input/train/"
sub_folders = check_output(["ls", train_path]).decode("utf8").strip().split('\n')
for sub_folder in sub_folders:
    file_names = check_output(["ls", train_path+sub_folder]).decode("utf8").strip().split('\n')
    k=0
    _, ax = plt.subplots(2,2,figsize=(10, 7))
    for file_name in [file_names[x] for x in indexes]: # I take only 4 images of each group. 
        img = cv2.imread(train_path+sub_folder+"/"+file_name,0)
        img2 = img
        w, h = template.shape[::-1]
        # Apply template Matching
        res = cv2.matchTemplate(img,template,method)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        top_left = max_loc
        bottom_right = (top_left[0] + w, top_left[1] + h)
 
        cv2.rectangle(img,top_left, bottom_right, 255, 2)
        if k==0 : 
            ax[0,0].imshow(img,cmap = 'gray')
            plt.xticks([]), plt.yticks([])
        if k==1 : 
            ax[0,1].imshow(img,cmap = 'gray')
            plt.xticks([]), plt.yticks([])
        if k==2 : 
            ax[1,0].imshow(img,cmap = 'gray')
            plt.xticks([]), plt.yticks([])
        if k==3 : 
            ax[1,1].imshow(img,cmap = 'gray')
            plt.xticks([]), plt.yticks([])
        k=k+1
    plt.suptitle(sub_folder)
    plt.show()

Lorsque vous l'exécutez, vous pouvez voir que seules les données de poissons "LAG" utilisées dans le modèle peuvent être segmentées avec une précision relativement élevée. Vous voudrez peut-être préparer un autre modèle pour d'autres poissons.

Recommended Posts

Segmentation et regroupement de photos avec DBSCAN
Clustering avec scikit-learn + DBSCAN
DBSCAN (clustering) avec scikit-learn
Segmentation d'image avec scikit-image et scikit-learn
Clustering avec python-louvain
DBSCAN avec scikit-learn
Clustering avec scikit-learn (1)
Clustering avec scikit-learn (2)
Filtrage coordonné avec analyse des composants principaux et clustering K-means
Apprentissage des données relationnelles avec numpy et NetworkX (clustering spectral)
Algorithme DBSCAN (clustering de données)
Pratiques et algorithmes DBSCAN
Avec et sans WSGI
Effectuer (Visualisation> Clustering> Description des fonctionnalités) avec (t-SNE, DBSCAN, Arbre de décision)
Avec moi, cp et sous-processus
Programmation avec Python et Tkinter
Chiffrement et déchiffrement avec Python
Travailler avec le tkinter et la souris
Python et matériel - Utilisation de RS232C avec Python -
J'ai essayé le clustering avec PyCaret
Binariser les données photo avec OpenCV
Super résolution avec SRGAN et ESRGAN
Group_by avec sqlalchemy et sum
python avec pyenv et venv
Clustering embarqué profond avec Chainer 2.0
Avec moi, NER et Flair
Fonctionne avec Python et R
Apprenez mnist non supervisé avec l'encodeur automatique et le cluster et évaluez les variables latentes