[PYTHON] Création d'un outil de recadrage d'image avec OpenCV, précautions pour la conversion de projection (seulement un peu)

introduction

J'ai créé un outil de recadrage d'images en utilisant OpenCV. Coupez l'objet, projetez-le, façonnez-le et enregistrez chacun d'eux.

L'objet est supposé être un film avec l'image suivante. Même si ce n'est pas le cas, je pense qu'il peut être utilisé lors de l'extraction d'un rectangle.

<img width="200", alt="sample.jpg ", src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/f532ad11-9c6d-e7df-844b-a30e6a2851ea.jpeg ">       <img width="100", alt="sample2.png ", src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/528427a2-9619-e2d1-b189-8a513cf87c64.jpeg ">

De plus, comme je suis tombé sur la conversion de projection lors de la création, le contenu et la solution sont décrits à la fin. Détails

environnement

Mac OS python 3.8.5

opencv-python 4.4.0.44 numpy 1.19.2 tqdm 4.50.2

python


pip install opencv-python
pip install tqdm

J'importe tqdm pour utiliser la barre de progression.

Conversion de projection

C'est un processus pour corriger l'objet comme s'il avait été pris de face. Je l'ai utilisé parce que je voulais que l'image recadrée soit à angle droit.

<img width="350", alt="射影変換後.jpeg ", src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/5b69d421-8b17-d09c-b6d4-e7843473ccb2.jpeg ">

Article de référence pour la conversion de projection La correction d'image est facile avec la transformation de projection Python / OpenCV! | WATLAB -Python, traitement du signal, AI- Essayez la conversion par projection d'images en utilisant OpenCV avec Python --Qiita

Texte intégral

Je me réfère aux articles suivants pour rendre les chemins japonais compatibles avec la lecture d'images. À propos du traitement des problèmes lors de la gestion des chemins de fichiers, y compris le japonais dans Python OpenCV cv2.imread et cv2.imwrite --Qiita

La méthode de fonctionnement est

  1. Créez un dossier de ressources dans la même hiérarchie que le script
  2. Placez l'image que vous souhaitez traiter dans le dossier de ressources (plusieurs possibles, jpg, jpeg ou png)
  3. Exécuter

Le résultat sera enregistré sous "./ dossier de résultats / nom du fichier image / nom du fichier image_0, ...". Si le même dossier que le nom de fichier existe, le processus est ignoré.

Si vous ne pouvez pas le couper correctement, essayez de changer thresh_value ou minimum_area. [Traitement des seuils d'image - documentation OpenCV-Python Tutorials 1](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_thresholding/py_thresholding. html) [Fonctionnalités de zone (contour) - documentation OpenCV-Python Tutorials 1](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_contours/py_contour_features/ py_contour_features.html)

python


import os, shutil, time
from pathlib import Path
import cv2
import numpy as np
from tqdm import tqdm

thresh_value = 240  #Valeur limite lors de la binarisation,Si la valeur du pixel est plus petite que cela, rendez-le blanc(max:255)
minimum_area = 10000  #Ne traitez pas d'objets plus petits que cela lors de l'acquisition du contour(Pour lorsque des points autres que l'objet cible sont détectés)


def imread(filename, flags=cv2.IMREAD_COLOR, dtype=np.uint8):
    try:
        n = np.fromfile(filename, dtype)
        img = cv2.imdecode(n, flags)
        return img
    except Exception as e:
        print(e)
        return None


def imwrite(filename, img, params=None):
    try:
        ext = os.path.splitext(filename)[1]
        result, n = cv2.imencode(ext, img, params)

        if result:
            with open(filename, mode='w+b') as f:
                n.tofile(f)
            return True
        else:
            return False
    except Exception as e:
        print(e)
        return False


def calculate_width_height(pts, add):
    """
Largeur de la forme détectée,Trouvez la hauteur en utilisant trois carrés
S'il est trop incliné, la forme changera lors de la conversion de la projection.

    :parameter
    ------------
    pts: numpy.ndarray
Coordonnées de 4 points de la forme extraite, shape=(4, 1, 2)
    add: int
Correction car les coordonnées du point de départ diffèrent selon la forme

    :return
    ------------
    width: int
Largeur calculée
    height: int
Hauteur calculée
    """
    top_left_cood = pts[0 + add][0]
    bottom_left_cood = pts[1 + add][0]
    bottom_right_cood = pts[2 + add][0]

    width = np.int(np.linalg.norm(bottom_left_cood - bottom_right_cood))
    height = np.int(np.linalg.norm(top_left_cood - bottom_left_cood))

    return width, height


def img_cut():
    """
Images dans le dossier de ressources(jpg, png)Pour obtenir le contour de l'objet
Coupez l'objet, projetez-le et amenez-le à l'avant

    1.dossier,Lire le fichier
    2.Lecture d'image,Binarisation(Noir et blanc)En traitement
    3.Obtenez un contour
    4.Conversion de projection
    5.production
    6.Déplacer le fichier de ressources vers le résultat

    :return: None
    """

    # 1.dossier,Lire le fichier
    resource_folder = Path(r'./resource')
    result_folder = Path(r'./result')
    #Créer si le dossier de résultats n'existe pas
    if not result_folder.exists():
        result_folder.mkdir()

    img_list1 = list(resource_folder.glob('*.jpg'))  #Liste des chemins des fichiers jpg dans le dossier
    img_list2 = list(resource_folder.glob('*.jpeg'))
    img_list3 = list(resource_folder.glob('*.png'))
    img_list = img_list1 + img_list2 + img_list3

    for img in img_list:
        img_name, img_suffix = img.stem, img.suffix  #Obtenir le nom et l'extension de l'image

        #Créez un dossier avec le nom du fichier image dans le dossier de résultats,Ignorer la conversion si le même dossier existe déjà
        result_img_folder = Path(r'./result/{}'.format(img_name))
        if not result_img_folder.exists():
            result_img_folder.mkdir()
        else:
            print('{}Ne peut pas être converti car un dossier portant le même nom que celui existant dans result'.format(img_name))
            continue

        # 2.Lecture d'image,Binarisation(Noir et blanc)En traitement
        read_img = imread(str(img))
        gray_img = cv2.cvtColor(read_img, cv2.COLOR_BGR2GRAY)
        ret, thresh_img = cv2.threshold(gray_img, thresh_value, 255, cv2.THRESH_BINARY_INV)

        # --------------------------------------------
        #Pour la confirmation d'image binarisée
        # cv2.namedWindow('final', cv2.WINDOW_NORMAL)
        # cv2.imshow('final', thresh_img)
        # cv2.waitKey(0)
        # cv2.destroyAllWindows()
        # --------------------------------------------

        # 3.Obtenez un contour
        # cv2.RETR_EXTERNAL:Extraire uniquement le contour le plus extérieur des contours détectés->Ignorer les contours même s'ils sont à l'intérieur du contour
        # cv2.CHAIN_APPROX_SIMPLE:Obtenez seulement 4 coins, pas les bords du contour
        contours, hierarchy = cv2.findContours(thresh_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        process_cnt = []  #Liste des contours à couper réellement
        for cnt in contours:
            if cv2.contourArea(cnt) < minimum_area:  #Ne coupez pas d'objets dont la zone de contour est trop petite
                continue
            process_cnt.append(cnt)

        num = 0
        for p_cnt in tqdm(process_cnt[::-1], desc='{}'.format(img_name)):  #Pour une raison quelconque, le processus commence à partir de l'image du bas, alors coupez-la en sens inverse.(Vers le haut)Correction de
            x, y, w, h = cv2.boundingRect(p_cnt)  #En haut à gauche du contour x,coordonnée y&largeur,Obtenez de la hauteur
            img_half_width = x + w / 2

            # cv2.arcLength:Longueur périphérique du contour,True signifie que le contour est fermé
            # cv2.approPolyDP:Forme approximative détectée
            epsilon = 0.1 * cv2.arcLength(p_cnt, True)
            approx = cv2.approxPolyDP(p_cnt, epsilon, True)
            try:
                # 4.Conversion de projection
                pts1 = np.float32(approx)
                if pts1[0][0][0] < img_half_width:  #Si le point de départ des coordonnées stockées dans pts est le coin supérieur gauche
                    width, height = calculate_width_height(pts1, 0)
                    pts2 = np.float32([[0, 0], [0, height], [width, height], [width, 0]])
                else:
                    width, height = calculate_width_height(pts1, 1)
                    pts2 = np.float32([[width, 0], [0, 0], [0, height], [width, height]])
            except IndexError:
                continue
            M = cv2.getPerspectiveTransform(pts1, pts2)
            dst = cv2.warpPerspective(read_img, M, (width, height))

            result_img_name = img_name + '_{}.{}'.format(num, img_suffix)
            imwrite(str(result_img_folder) + '/' + result_img_name, dst)

            num += 1
        # 6.Déplacer le fichier de ressources vers le résultat
        shutil.move(str(img), result_img_folder)


if __name__ == '__main__':
    img_cut()
    print('Fin d'exécution')
    time.sleep(3)

Détails

python


# cv2.arcLength:Longueur périphérique du contour,True signifie que le contour est fermé
# cv2.approPolyDP:Forme approximative détectée
epsilon = 0.1 * cv2.arcLength(p_cnt, True)
approx = cv2.approxPolyDP(p_cnt, epsilon, True)
try:
    # 4.Conversion de projection
    pts1 = np.float32(approx)

Les coordonnées des 4 coins de l'objet sont stockées dans pts1. ex) [[[6181. 598.]]

[[ 145. 656.]]

[[ 135. 3499.]]

[[6210. 3363.]]]

En amenant ces quatre points aux coins de l'image, l'image semble avoir été vue de face.

python


if pts1[0][0][0] < img_half_width:  #Si le point de départ des coordonnées stockées dans pts est le coin supérieur gauche
    width, height = calculate_width_height(pts1, 0)
    pts2 = np.float32([[0, 0], [0, height], [width, height], [width, 0]])
else:
    width, height = calculate_width_height(pts1, 1)
    pts2 = np.float32([[width, 0], [0, 0], [0, height], [width, height]])

Il détermine où se trouve le point de départ de pts1. Étant donné que les quatre points de coordonnées stockés sont stockés dans le sens inverse des aiguilles d'une montre à partir du point supérieur (l'axe des y est petit), le point de départ de pts1 change en fonction de l'inclinaison de l'image. Il est jugé et fait correspondre aux coordonnées pts2 après conversion de projection. <img width="400", alt="pts座標.png ", src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/f6c5e9a7-d49c-45ca-191e-05efa952f14a.png ">

La méthode de jugement est basée sur le fait que la coordonnée x du point de départ se trouve à gauche ou à droite du centre de l'image.

De plus, comme je veux garder la forme de l'objet autant que possible, j'ai calculé la largeur et la hauteur en utilisant trois carrés et j'ai sorti une image recadrée avec cette taille.

À la fin

Je ne savais pas que le point de départ de pts1 allait changer et au début, je produisais une image dénuée de sens. Je ne pouvais pas le trouver facilement même si je cherchais sur le net, et à la fin j'étais dans un état où je l'ai finalement découvert en regardant l'image. J'espère que cela sera utile lorsqu'il y a des gens qui ont des problèmes similaires.

Site de référence

Didacticiel [Traitement des seuils d'image - documentation OpenCV-Python Tutorials 1](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_thresholding/py_thresholding. html) Plan: Première étape - documentation des didacticiels OpenCV-Python 1 [Fonctionnalités de zone (contour) - documentation OpenCV-Python Tutorials 1](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_contours/py_contour_features/ py_contour_features.html)

Référence OpenCV (contour, rognage d'image, conversion de projection) Bases de la détection de contour à partir d'images avec OpenCV (par fonction findContours) | North Building Tech.com [Obtenir et rogner des objets dans une image en utilisant contours-python, image, opencv-contour](https://living-sun.com/ja/python/725302-getting-and-cropping-object-in] -images-utilisant-des-contours-python-image-opencv-contour.html) Essayez la conversion par projection d'images en utilisant OpenCV avec Python --Qiita

Prise en charge de la lecture des pass japonais OpenCV À propos du traitement des problèmes lors de la gestion des chemins de fichiers, y compris le japonais dans Python OpenCV cv2.imread et cv2.imwrite --Qiita

référence de calcul numpy trois carrés [Calculer la distance euclidienne](http://harmonizedai.com/article/%E3%83%A6%E3%83%BC%E3%82%AF%E3%83%AA%E3%83%83%E3% 83% 89% E8% B7% 9D% E9% 9B% A2% E3% 82% 92% E6% B1% 82% E3% 82% 81% E3% 82% 8B /)

Référence de la barre de progression Afficher la barre de progression avec tqdm --Qiita

Recommended Posts

Création d'un outil de recadrage d'image avec OpenCV, précautions pour la conversion de projection (seulement un peu)
Traitement d'image avec Lambda + OpenCV (création d'image grise)
Outil de rognage d'image GUI réalisé avec Python + Tkinter
Problèmes lors de la création d'un outil de conversion csv-json avec python
Créez un outil d'analyse vidéo simple avec python wxpython + openCV
[Python] Accès et recadrage des pixels d'image à l'aide d'OpenCV (pour les débutants)