3
votes

Recadrer la zone de l'image à l'aide de Pillow en Python

 Rectangle avec des lignes qui ne sont pas parallèles aux marges

Je souhaite recadrer une zone en forme de rectangle à partir d'une image à l'aide de Pillow en python. Le problème est que le rectangle n'est pas nécessairement parallèle aux marges de l'image, donc je ne peux pas utiliser la fonction .crop ((gauche, haut, droite, bas)).

Y a-t-il un moyen d'y parvenir avec Pillow? (en supposant que nous connaissons les coordonnées des 4 points du rectangle) Sinon, comment cela peut-il être fait en utilisant une autre bibliothèque Python?

 Le résultat final devrait ressembler à ceci


2 commentaires

Veuillez fournir les coordonnées que vous avez vraisemblablement pour les coins. À quoi vous attendez-vous du résultat?


À quoi vous attendriez-vous? l'image avec des pixels noirs à l'extérieur du rectangle? ou une image rectifiée?


3 Réponses :


2
votes

Vous pouvez utiliser le rectangle pivoté min dans OpenCV:

rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)

En conséquence, vous avez: coordonnées du centre (x, y), largeur, hauteur, angle de rotation du rectangle. Vous pouvez faire pivoter l'image entière avec l'angle de ce rectangle. Votre image va maintenant pivoter:

 Image pivotée

Vous pouvez calculer de nouvelles coordonnées de quatre sommets de rectangle (vous avez un angle). Ensuite, calculez simplement le rectangle normal pour ces points (rectangle normal = non minimal, sans aucune rotation). Avec ce rect, vous pouvez recadrer votre image pivotée. Dans cette image recadrée sera ce que vous voulez si je vous comprends correctement. Quelque chose comme ça:

 result

Vous n'avez donc besoin que d'Opencv. Il existe peut-être une bibliothèque avec laquelle vous pouvez le faire plus facilement.


0 commentaires

2
votes

Voici une solution basée sur scikit-image (pas Pillow) que vous pourriez trouver utile.

Vous pouvez passer les sommets de la région que vous souhaitez rogner à la fonction skimage.draw.polygon puis utilisez le fichier récupéré coordonnées en pixels pour masquer l'image d'origine (par exemple, via le canal alpha).

import numpy as np
from skimage import io, draw

img = io.imread('https://i.stack.imgur.com/x5Ym4.png')

vertices = np.asarray([[150, 140],
                       [300, 240],
                       [210, 420],
                       [90, 320],
                       [150, 150]])

rows, cols = draw.polygon(vertices[:, 0], vertices[:, 1])

crop = img.copy()
crop[:, :, -1] = 0
crop[rows, cols, -1] = 255

io.imshow(crop)

 Image masquée


0 commentaires

0
votes

J'ai adapté ce opencv Solution basée sur ( sub_image ) à utiliser avec PIL . Il prend un rect (center, size, theta) que j'obtiens de cv2.minAreaRect , mais pourrait être construit de manière mathématique à partir de points, etc.

I ' J'ai vu quelques autres solutions mais ils ont laissé des artefacts étranges.

     
def crop_tilted_rect(image, rect):
    """ crop rect out of image, handing rotation
    
    rect in this case is a tuple of ((center_x, center_y), (width, height), theta),
    which I get from opencv's cv2.minAreaRect(contour)
    """
    # Get center, size, and angle from rect
    center, size, theta = rect
    width, height = [int(d) for d in size]

    if 45 < theta <= 90:
        theta = theta - 90
        width, height = height, width

    theta *= math.pi / 180 # convert to rad
    v_x = (math.cos(theta), math.sin(theta))
    v_y = (-math.sin(theta), math.cos(theta))
    s_x = center[0] - v_x[0] * (width / 2) - v_y[0] * (height / 2)
    s_y = center[1] - v_x[1] * (width / 2) - v_y[1] * (height / 2)
    mapping = np.array([v_x[0],v_y[0], s_x, v_x[1],v_y[1], s_y])
    return image.transform((width, height), Image.AFFINE, data=mapping, resample=0, fill=1, fillcolor=(255,255,255))


0 commentaires