[PYTHON] Fotosegmentierung und Clustering mit DBSCAN

Einführung

Dieser Artikel ist Teil des Beispielcodes für Kaggle: The Nature Conservancy Fisheries Monitoring. Das auf CNN bezogene Programm ist hier nicht aufgeführt. Details werden zu einem späteren Zeitpunkt zusammengefasst.

Hier werden wir zwei Analysen anhand der Fotos des Angelplatzes durchführen.

  1. Schätzen Sie eine eindeutige ID für jedes Boot
  2. Segmentieren Sie den Fischstandort auf dem Foto

1. Clustering von Booten, die Fotos gemacht haben

Hier stellen wir vor Wie man automatisch die Arten von Booten gruppiert, die Bilder aufgenommen haben. Beispielsweise kann es verwendet werden, um für jedes Boot ein anderes Modell zu erstellen oder Bereiche zu maskieren, in denen sich keine Fische auf dem Brett befinden.

Erstellen Sie eine Funktion zum Anzeigen von Bildern nebeneinander. Es gibt zwei Arten, vier und acht. Danach werden 500 Proben aus dem Zug gelesen.

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)))

Die Größe der Zugbilder ist nicht einheitlich. Gibt es eine Fotogröße, die nur bestimmte Boote enthält? Lassen Sie uns die Boots-ID als Bildgröße überprüfen.

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

Die Größe geteilt. Lassen Sie uns vier aktuelle Bilder gleichzeitig anzeigen.

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

Mit Ausnahme der Bildgröße von (854, 1518, 3) enthalten die anderen Bilder ein oder mehrere Boote. Ein anderer Ansatz ist wahrscheinlich erforderlich, um die Boots-ID zu berücksichtigen.

Boot-Clustering

Der Einfachheit halber werden wir diese 500 Daten analysieren. Natürlich kann dieselbe Verarbeitung mit allen Bilddaten durchgeführt werden. Analysieren Sie in den folgenden drei Schritten.

# 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

Erstellen Sie eine NxN-Matrix. N ist die Anzahl der Bilder, und diese Matrix zeigt den Abstand zwischen den Bildern. SKLearn verfügt über viele Clustering-Methoden, die vorberechnete Distanzmatrizen verwenden können. Hier wird die Distanzmatrix DBSCAN zum Clustering übergeben.

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

__results___9_1.png

Sie können sehen, dass es eine Fläche von 0,8 oder weniger gibt. Wahrscheinlich bei der Berechnung des Abstands zwischen Bildern desselben Bootes. DBSCAN betrachtet Entfernungen bis zu 0,5 als ähnliche Cluster. Wenn wir uns das Histogramm ansehen, beurteilen wir, dass 0,6 ein geeigneter Schwellenwert ist.

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()

Es hat ziemlich gut funktioniert. Es gab jedoch eine Gruppe, die keinem Bootsausweis angehörte. Es gibt zwei mögliche Gründe.

  1. Es sind nur 5 oder weniger Bilder als Schwellenwert für die Boots-ID festgelegt
  2. Die Distanzfunktion reicht nicht zum Clustering von Bildern aus. Beispielsweise werden Nacht- und Tagesfotos gemischt.
size = len(np.array(train)[y == -1])
show_eight(np.array(train)[y == -1], 'BoatID: {} (Unclassified images) - Image count {}'.format(-1, size))

Einige können visuell Cluster erstellen. Mit anderen Worten, der Algorithmus kann verbessert werden. Die Tatsache, dass mehr als 75% der Boote durch unbeaufsichtigtes Lernen klassifiziert werden konnten, bedeutet jedoch, dass die Klassifizierung ziemlich genau war.

2. Fischsegmentierung

Segmentieren Sie das Fischbild. Als Arbeitsablauf

  1. Bereiten Sie ein Fischfoto als Vorlage vor
  2. Wählen Sie ein anderes Foto als die Vorlage aus und wählen Sie die entsprechende Segmentierungsmethode mit mehreren Algorithmen aus
  3. Segmentieren Sie mehrere Fotos mit dem zuvor ausgewählten Algorithmus
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')

Verwenden Sie andere Daten als das zuvor als Vorlage verwendete Foto. Mithilfe der matchTemplate von opencv können Sie einen Teil finden, der der vorbereiteten Vorlage ähnelt. Es gibt verschiedene Arten von optionalen Methoden, daher werden wir mit jeder der sechs Methoden experimentieren. Der angegebene Ort soll von einem Quadrat umgeben sein.

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()

Wenn ich es ausführe, kann ich den Fisch gut mit anderen Methoden als TM_SQDIFF und TM_SQDIFF_NORMED segmentieren. Daher wird dieses Mal TM_CCOEFF als Erkennungsmethode verwendet.

Die Fotos in diesem Wettbewerb sind auf 8 Fischarten beschränkt. Wählen Sie daher 4 Fotos von jeder Fischart aus und führen Sie die Segmentierung mit TM_CCOEFF durch.

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()

Wenn Sie es ausführen, können Sie sehen, dass nur die in der Vorlage verwendeten Fischdaten "LAG" mit relativ hoher Genauigkeit segmentiert werden können. Möglicherweise möchten Sie eine andere Vorlage für andere Fische vorbereiten.

Recommended Posts

Fotosegmentierung und Clustering mit DBSCAN
Clustering mit scikit-learn + DBSCAN
DBSCAN (Clustering) mit Scikit-Learn
Bildsegmentierung mit Scikit-Image und Scikit-Learn
Clustering mit Python-Louvain
DBSCAN mit Scikit-Learn
Clustering mit Scikit-Learn (1)
Clustering mit Scikit-Learn (2)
Koordinierte Filterung mit Hauptkomponentenanalyse und K-Mittel-Clustering
Lernen von Beziehungsdaten mit Numpy und NetworkX (Spektralclustering)
DBSCAN-Algorithmus (Datenclustering)
DBSCAN-Praktiken und -Algorithmen
Mit und ohne WSGI
Führen Sie (Visualisierung> Clustering> Funktionsbeschreibung) mit (t-SNE, DBSCAN, Entscheidungsbaum) durch.
Bei mir cp und Subprocess
Programmieren mit Python und Tkinter
Ver- und Entschlüsselung mit Python
Arbeiten Sie mit tkinter und Maus
Python und Hardware-Verwenden von RS232C mit Python-
Ich habe versucht, mit PyCaret zu clustern
Binarisieren Sie Fotodaten mit OpenCV
Super Auflösung mit SRGAN und ESRGAN
Group_by mit sqlalchemy und sum
Python mit Pyenv und Venv
Deep Embedded Clustering mit Chainer 2.0
Mit mir, NER und Flair
Funktioniert mit Python und R.
Lernen Sie mnist unbeaufsichtigt mit Auto Encoder und Cluster und werten Sie latente Variablen aus