So entfernen Sie verschwommene Fotos mit Python

Wenn wir eine große Serie von Aufnahmen machen, sind einige davon unscharf. Ein großes Automobilunternehmen stand vor dem gleichen Problem. Einige der Fotos während der Inspektion des Autos waren verschwommen, was sich negativ auf den Verkauf auswirken könnte.

Bilder von geringer Qualität reduzieren direkt den Gewinn.


  • Wie erkennt eine Anwendung unscharfe Fotos auf Algorithmenebene?
  • Wie kann man die Klarheit eines RGB-Bildes messen?



Formulierung des Problems


Ich arbeite als Analyst in einem großen Automobilunternehmen. Bei der Inspektion eines Autos, bei der Inspektion eines Autos, machen sie viele Fotos über eine spezielle Anwendung, die sofort an die Datenbank gesendet werden. Einige Bilder sind verschwommen, was für den Verkauf schlecht ist.

Von hier aus ergibt sich das Problem: "Wie erkennt man unscharfe Bilder auf Algorithmenebene?"

Entwicklung eines Algorithmus basierend auf einer Stichprobe von 1200 Fotos verschiedener Elemente von Autos. Ein Merkmal des Beispiels ist, dass es nicht beschriftet ist, weil Es ist schwer zu bestimmen, welche Bilder klar sind und welche nicht.

Es stellt sich heraus, dass das Erlernen des ML-Modells „mit einem Lehrer“ für die Lösung nicht anwendbar ist.

Im Laufe der Arbeit habe ich Werkzeuge verwendet:

  • Python . Bibliotheken: numpy, matplotlib, cv2;
  • Jupyter Notizbuch.

In dem Artikel werde ich die Lösung des Problems beschreiben, zu dem ich gekommen bin.

Beschreibung des Lösungsansatzes


Stufe 1. Grenzen definieren


Welches Foto kann als klar bezeichnet werden?
Eine, in der die Grenzen von Objekten ausgesprochen werden. Bei unscharfen Aufnahmen verschwimmen die Ränder von Objekten.

Wie bestimme ich die Grenzen von Objekten im Bild?

Rahmen, an denen wir den größten Farbunterschied sehen.

Es stellt sich heraus, dass Sie zur Bestimmung der Klarheit des Bildes zuerst die Grenzen der Objekte von Fotografien bestimmen und dann deren Größe, Dicke, Anzahl usw. bewerten müssen.

Das Foto besteht aus einer dreidimensionalen Anordnung von Zahlen von 0 bis 255: (Breite, Höhe, 3 Farben).
Ich definierte die Grenzen durch Anwenden eines Filters wie beim Erstellen eines tiefen neuronalen Netzwerks: durch Multiplizieren eines dreidimensionalen Arrays mit Matrizen (für jede Farbe):

    │ 1 -1 │
    │ 1 -1 │

Mit einem Farbunterschied erzeugt das resultierende Array eine hohe Modulzahl.
Also definieren wir die vertikalen und horizontalen Grenzen. Das arithmetische Mittel zeigt die gemeinsamen Grenzen der Fotografie.

Stufe 2. Analyse der Grenzen zur Klarheit


Die Grenzen sind definiert.

Wie kann man den Rand eines unscharfen Bildes vom Rand eines klaren Bildes unterscheiden?

Bei verschiedenen Optionen habe ich folgenden Ansatz gefunden:

  1. Definieren Sie die Grenzen des Originalfotos (beschrieben in Schritt 1).
  2. verwischen Sie das Originalbild;
  3. Definieren Sie die Grenzen des verschwommenen Bildes (beschrieben in Schritt 1).
  4. wir betrachten das Verhältnis des arithmetischen Mittels von Absatz 1 und Absatz 2;
  5. Der resultierende Koeffizient kennzeichnet die Klarheit des Bildes.

Die Logik ist einfach: Bei klaren Fotos tritt die Änderung der Ränder signifikanter auf als bei unscharfen, was bedeutet, dass der Koeffizient höher ist.

Python-Implementierung des Algorithmus


Um das Problem direkt zu lösen, verwenden wir die folgenden Bibliotheken:

import numpy as np
import matplotlib.pyplot as plt
import cv2

Für die Parameter zur Bestimmung der Grenzen definieren wir die Matrixdefinitionsfunktion:

def edges(n, orient):
    edges = np.ones((2*n, 2*n, 3))
    
    if orient == 'vert':
        for i in range(0, 2*n):
            edges[i][n: 2*n] *= -1
    elif orient == 'horiz':
        edges[n: 2*n] *= -1
    
    return edges

Unter dem Parameter n geben wir die Anzahl der Pixel an, die wir in die Grenzschätzung einbeziehen. Die Ausrichtung der Matrix kann horizontal oder vertikal sein.

Weitere Funktionen ähneln einer tiefen neuronalen Netzwerkschicht:

# Apply one filter defined by parameters W and single slice
def conv_single_step(a_slice_prev, W):
    s = W * a_slice_prev
    Z = np.sum(s)
    Z = np.abs(Z)
    
    return Z
   
# Full edge filter
def conv_forward(A_prev, W, hparameters):
    m = len(A_prev)
    (f, f, n_C) = W.shape
    stride = hparameters['stride']
    pad = hparameters['pad']
    
    Z = list()
    flag = 0
    z_max = hparameters['z_max']
    
    if len(z_max) == 0:
        z_max = list()
        flag = 1
    
    for i in range(m):
        
        (x0, x1, x2) = A_prev[i].shape
        A_prev_pad = A_prev[i][ 
                            int(x0 / 4) : int(x0 * 3 / 4), 
                            int(x1 / 4) : int(x1 * 3 / 4), 
                            :]
        
        (n_H_prev, n_W_prev, n_C_prev) = A_prev_pad.shape
        n_H = int((n_H_prev - f + 2*pad) / stride) + 1
        n_W = int((n_W_prev - f + 2*pad) / stride) + 1
        z = np.zeros((n_H, n_W))
        
        a_prev_pad = A_prev_pad
        
        for h in range(n_H):
            vert_start = h * stride
            vert_end = h * stride + f
            
            for w in range(n_W):
                horiz_start = w * stride
                horiz_end = w * stride + f
                
               
                a_slice_prev = a_prev_pad[vert_start: vert_end, horiz_start: horiz_end, :]

                weights = W[:, :, :]
                z[h, w] = conv_single_step(a_slice_prev, weights)
        
        if flag == 1:
            z_max.append(np.max(z))
        Z.append(z / z_max[i])
        
    cache = (A_prev, W, hparameters)
    
    return Z, z_max, cache

# pooling
def pool_forward(A_prev, hparameters, mode = 'max'):
    m = len(A_prev)
    f = hparameters['f']
    stride = hparameters['stride']
    
    A = list()
    
    for i in range(m):
        (n_H_prev, n_W_prev) = A_prev[i].shape
        
        n_H = int(1 + (n_H_prev - f) / stride)
        n_W = int(1 + (n_W_prev - f) / stride)
        
        a = np.zeros((n_H, n_W))
        
        for h in range(n_H):
            vert_start = h * stride
            vert_end = h * stride + f
            
            for w in range(n_W):
                horiz_start = w * stride
                horiz_end = w * stride + f
                
                a_prev_slice = A_prev[i][vert_start: vert_end, horiz_start: horiz_end]

                if mode == 'max':
                    a[h, w] = np.max(a_prev_slice)
                elif mode == 'avg':
                    a[h, w] = np.mean(a_prev_slice)
                        
        A.append(a)

    cache = (A_prev, hparameters)
    
    return A, cache

conv_single_step - eine Multiplikation der Bildfarben mit Matrizen, die den Rand enthüllen.
conv_forward - Eine vollständige Definition der Ränder im gesamten Foto.
pool_forward - Reduziert die Größe des resultierenden Arrays.

Separat notiere ich den Wert der Zeilen in der Funktion conv_forward:

(x0, x1, x2) = A_prev[i].shape
A_prev_pad = A_prev[i][ 
    int(x0 / 4) : int(x0 * 3 / 4), 
    int(x1 / 4) : int(x1 * 3 / 4), 
    :]

Für die Analyse verwenden wir nicht das gesamte Bild, sondern nur seinen zentralen Teil, weil Die Kamera fokussiert häufiger auf die Mitte. Wenn das Bild klar ist, ist die Mitte klar.

Die folgende Funktion bestimmt die Grenzen von Objekten im Bild mithilfe der vorherigen Funktionen:

# main layer
def borders(images, filter_size = 1, stride = 1, pool_stride = 2, pool_size = 2, z_max = []):
    Wv = edges(filter_size, 'vert')
    hparameters = {'pad': pad, 'stride': stride, 'pool_stride': pool_stride, 'f': pool_size, 'z_max': z_max}
    Z, z_max_v, _ = conv_forward(images, Wv, hparameters)
    
    print('edge filter applied')
    
    hparameters_pool = {'stride': pool_stride, 'f': pool_size}
    Av, _ = pool_forward(Z, hparameters_pool, mode = 'max')
    
    print('vertical filter applied')
    
    Wh = edges(filter_size, 'horiz')
    hparameters = {'pad': pad, 'stride': stride, 'pool_stride': pool_stride, 'f': pool_size, 'z_max': z_max}
    Z, z_max_h, _ = conv_forward(images, Wh, hparameters)
    
    print('edge filter applied')
    
    hparameters_pool = {'stride': pool_stride, 'f': pool_size}
    Ah, _ = pool_forward(Z, hparameters_pool, mode = 'max')
    
    print('horizontal filter applied')   
    
    return [(Av[i] + Ah[i]) / 2 for i in range(len(Av))], list(map(np.max, zip(z_max_v, z_max_h)))

Die Funktion bestimmt die vertikalen und dann die horizontalen Grenzen und gibt das arithmetische Mittel beider Arrays zurück.

Und die Hauptfunktion für die Ausgabe des Definitionsparameters:

# calculate borders of original and blurred images
def orig_blur(images, filter_size = 1, stride = 3, pool_stride = 2, pool_size = 2, blur = 57):
    z_max = []

    img, z_max = borders(images, 
                         filter_size = filter_size, 
                         stride = stride, 
                         pool_stride = pool_stride, 
                         pool_size = pool_size
                        )
    print('original image borders is calculated')
    
    blurred_img = [cv2.GaussianBlur(x, (blur, blur), 0) for x in images]
    print('images blurred')
    
    blurred, z_max = borders(blurred_img, 
                             filter_size = filter_size, 
                             stride = stride, 
                             pool_stride = pool_stride, 
                             pool_size = pool_size, 
                             z_max = z_max
                            )
    print('blurred image borders is calculated')

    return [np.mean(orig) / np.mean(blurred) for (orig, blurred) in zip(img, blurred)], img, blurred

Zuerst bestimmen wir die Grenzen des Originalbildes, verwischen dann das Bild, dann bestimmen wir die Grenzen des verschwommenen Fotos und schließlich betrachten wir das Verhältnis der arithmetischen Mittelgrenzen des Originalbildes und der Unschärfe.

Die Funktion gibt eine Liste von Definitionsfaktoren, ein Array von Rändern des Originalbilds und ein Array von verschwommenen Rändern zurück.

Beispiel für eine Algorithmusoperation


Zur Analyse habe ich Bilder aus dem Fotobestand von freepik.com aufgenommen.









Wir bestimmen die Grenzen des ersten Bildes vor und nach der Unschärfe:



Zweitens:





Drittens:





Viertens:





In den Bildern ist zu sehen, dass die Randänderungen für klare Bilder (3. und 4.) stärker sind als für unscharfe (1. und 2.).

Nach Berechnungen erhalten wir die Koeffizienten:

[5.92918651681958,
2.672756123184502,
10.695051017699232,
11.901115749698139]

Die Koeffizienten bestätigen die Schlussfolgerungen: Je größer der Koeffizient, desto schärfer das Foto.
Darüber hinaus ist das zweite Bild weniger klar als das erste, was sich in den Koeffizienten widerspiegelt.

Annäherungsfunktionen


  • Je schärfer das Bild, desto stärker ändert sich der Rand, dh je höher der Parameter.
  • Für unterschiedliche Bedürfnisse ist unterschiedliche Klarheit erforderlich. Daher ist es notwendig, die Grenzen der Klarheit selbst zu bestimmen: Irgendwo liegt der Koeffizient für ausreichend klare Fotos über 7, irgendwo nur über 10;
  • Der Koeffizient hängt von der Helligkeit des Fotos ab. Die Ränder dunkler Fotos ändern sich schwächer, was bedeutet, dass der Koeffizient geringer ist. Es stellt sich heraus, dass die Grenzen der Klarheit unter Berücksichtigung der Beleuchtung, dh für Standardfotos, bestimmt werden müssen;

Ein funktionierender Algorithmus befindet sich auf meinem Github- Konto .

All Articles