Comment se débarrasser des photos floues en utilisant Python

Lorsque nous prenons une grande série de photos, certaines sont floues. Une grande entreprise automobile a été confrontée au même problème. Certaines des photographies lors de l'inspection de la voiture se sont révélées floues, ce qui pourrait avoir un impact négatif sur les ventes.

Les images de faible qualité réduisent directement les bénéfices.


  • Comment une application reconnaît-elle les photos floues au niveau de l'algorithme?
  • Comment mesurer la clarté d'une image RVB?



Formulation du problème


Je travaille comme analyste dans une grande entreprise automobile. Lors de l'inspection d'une voiture, lors de l'inspection d'une voiture, ils prennent beaucoup de photos via une application spéciale, qui sont immédiatement envoyées à la base de données. Certaines images sont floues, ce qui est mauvais pour les ventes.

De là, le problème se pose: "comment reconnaître les images floues au niveau de l'algorithme?"

Développer un algorithme basé sur un échantillon de 1200 photos de différents éléments de voitures. Une caractéristique de l'échantillon est qu'il n'est pas étiqueté, car il est difficile de déterminer quelles images sont claires et lesquelles ne le sont pas.

Il s'avère que l'apprentissage du modèle ML «avec un enseignant» n'est pas applicable à la solution.

Au cours du travail, j'ai utilisé des outils:

  • Python . Bibliothèques: numpy, matplotlib, cv2;
  • Cahier Jupyter .

Dans l'article, je décrirai la solution au problème auquel je suis arrivé.

Description de l'approche pour résoudre le problème


Étape 1. Définir les limites


Quelle photo peut être qualifiée de claire?
Celui dans lequel les limites des objets sont prononcées. Dans les photos floues, les bordures des objets sont floues.

Comment déterminer les limites des objets dans l'image?

Bordures où nous voyons la plus grande différence de couleur.

Il s'avère que pour déterminer la clarté de l'image, vous devez d'abord déterminer les limites des objets des photographies, puis évaluer leur taille, leur épaisseur, leur nombre, etc.

La photo se compose d'un tableau tridimensionnel de nombres de 0 à 255: (largeur, hauteur, 3 couleurs).
J'ai défini les limites en appliquant un filtre comme dans la création d'un réseau neuronal profond: en multipliant un tableau tridimensionnel par des matrices (pour chaque couleur):

    │ 1 -1 │
    │ 1 -1 │

Avec une différence de couleur, le réseau résultant produira un nombre de modules élevé.
Nous définissons donc les limites verticales et horizontales. La moyenne arithmétique montre les bords communs de la photographie.

Étape 2. Analyse des limites pour plus de clarté


Les limites sont définies.

Comment distinguer la bordure d'une image floue de la bordure d'un clair?

En passant par différentes options, j'ai trouvé l'approche suivante:

  1. définir les limites de la photo originale (décrite à l'étape 1);
  2. flouter l'image originale;
  3. définir les limites de l'image floue (décrite à l'étape 1);
  4. nous considérons le rapport de la moyenne arithmétique des paragraphes 1 et 2;
  5. le coefficient résultant caractérise la clarté de l'image.

La logique est simple: dans les photographies claires, le changement de bordures se produira plus significativement que dans les photos floues, ce qui signifie que le coefficient sera plus élevé.

Implémentation Python de l'algorithme


Pour résoudre le problème directement, nous utilisons les bibliothèques suivantes:

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

Pour les paramètres de détermination des limites, nous définissons la fonction de définition de la matrice:

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

Sous le paramètre n, nous spécifions le nombre de pixels que nous incluons dans l'estimation des bornes. L'orientation de la matrice peut être horizontale ou verticale.

D'autres fonctions sont similaires à une couche de réseau neuronal profond:

# 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 - une multiplication des couleurs de l'image par des matrices révélant la bordure.
conv_forward - Une définition complète des bordures sur la photo entière.
pool_forward - réduit la taille du tableau résultant.

Séparément, je note la valeur des lignes dans la fonction 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), 
    :]

Pour l'analyse, nous utilisons non pas l'image entière, mais seulement sa partie centrale, car la caméra se concentre plus souvent sur le centre. Si l'image est claire, le centre sera clair.

La fonction suivante détermine les limites des objets dans l'image à l'aide des fonctions précédentes:

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

La fonction détermine les limites verticales, puis horizontales, et renvoie la moyenne arithmétique des deux tableaux.

Et la fonction principale pour l'émission du paramètre de définition:

# 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

Tout d'abord, nous déterminons les limites de l'image d'origine, puis nous rendons l'image floue, puis nous déterminons les limites de la photo floue, et, enfin, nous considérons le rapport des limites moyennes arithmétiques de l'image originale et du flou.

La fonction renvoie une liste de facteurs de définition, un tableau de bordures de l'image d'origine et un tableau de bordures floues.

Exemple de fonctionnement d'algorithme


Pour l'analyse, j'ai pris des photos du stock de photos freepik.com.









Nous déterminons les limites de la première image avant et après le flou:



Deuxième:





Troisième:





Quatrième:





Dans les images, on voit que les changements de bordure pour les images claires (3e et 4e) sont plus forts que pour les images floues (1re et 2e).

Après les calculs, nous obtenons les coefficients:

[5.92918651681958,
2.672756123184502,
10.695051017699232,
11.901115749698139]

Les coefficients confirment les conclusions: plus le coefficient est grand, plus la photo est nette.
De plus, la deuxième image est moins claire que la première, ce qui se reflète dans les coefficients.

Caractéristiques d'approche


  • plus l'image est nette, plus la bordure change fort, ce qui signifie que plus le paramètre est élevé;
  • pour différents besoins, une clarté différente est nécessaire. Par conséquent, il est nécessaire de déterminer vous-même les limites de la clarté: quelque part, le coefficient de suffisamment de photos claires sera supérieur à 7, quelque part au-dessus de 10;
  • le coefficient dépend de la luminosité de la photo. Les bordures des photos sombres seront plus faibles, ce qui signifie que le coefficient sera inférieur. Il s'avère que les limites de clarté doivent être déterminées en tenant compte de l'éclairage, c'est-à-dire pour les photographies standard;

Un algorithme de travail peut être trouvé sur mon compte github .

All Articles