[PYTHON] Rendre la fonction de dessin de polices japonaises dans OpenCV en général

introduction

J'ai reçu un commentaire dans l'article précédent "Fonctionnalisation de la représentation des polices japonaises avec OpenCV" (https://qiita.com/mo256man/items/82da5138eeacc420499d). Je suis très reconnaissant. De plus, l'endroit où j'ai sauté est clairement visible. Je dois faire de mon mieux pour cela.

Amélioration

À propos de RVB et BGR

La cible est une fonction qui peut décrire le japonais de la même manière que cv2.imshow (), donc la couleur spécifiée est la séquence d'OpenCV (BGR). Dans l'article précédent, lorsque je travaillais avec PIL dans une fonction, je l'ai converti en une séquence de couleurs PIL (RVB). L'image a également été convertie de BGR en RVB, le japonais a été représenté à l'aide de la fonction PIL, renvoyé à BGR, puis reconverti en OpenCV.

Inversion de l'ordre des couleurs plus élégante

Concernant l'inversion de l'ordre des couleurs Dans ce qui précède, la méthode d'implémentation non élégante consistant à retirer chaque élément et à changer l'ordre a été utilisée, mais il semble que les tranches peuvent être utilisées normalement.

python


>>> (255, 0, 128)[::-1]
(128, 0, 255)

Quoi? J'aurais dû essayer cela après de nombreux essais et erreurs.

Ou plutôt, il n'est pas nécessaire d'inverser l'ordre des couleurs

alors. À la condition que vous compreniez la relation entre BGR et RVB. Si vous ne convertissez pas l'image dans la couleur correcte pour PIL, dessinez la séquence qui devrait être RVB comme couleur de texte avec PIL comme BGR et renvoyez-la à OpenCV telle quelle, ce sera la couleur correcte comme OpenCV en conséquence. C'est rapide, n'est-ce pas? C'est le but de la personne qui a commenté. Quand je l'ai soumis au professeur de l'école comme devoir, il a dit: "Je vous ai dit qu'OpenCV et PIL avaient la séquence de couleurs opposée. Cela semble être la bonne réponse mais la mauvaise formule au milieu." C'est une technique qui semble être dite, mais la programmation nécessite ce genre d'idée. Non, je l'ai aussi inventé. Cependant, je ne l'ai pas beaucoup intégré car je ne pouvais pas utiliser la fonction qui convertit OpenCV et PIL que j'ai créée. Excusez-moi.

Alors comparons la troisième fonction que vous avez publiée avec ma deuxième fonction.

python


#Fonction d'origine
def cv2_putText_2(img, text, org, fontFace, fontScale, color):
    x, y = org
    b, g, r = color        #Cette phrase est inutile car la couleur reste BGR
    colorRGB = (r, g, b)   #Cette phrase est inutile car la couleur reste BGR. N'utilisez même pas de tranches
    imgPIL = cv2pil(img)   #Le processus de conversion simple de la fonction originale de conversion de la couleur en PIL en PIL
    draw = ImageDraw.Draw(imgPIL)
    fontPIL = ImageFont.truetype(font = fontFace, size = fontScale)
    w, h = draw.textsize(text, font = fontPIL)
    draw.text(xy = (x,y-h), text = text, fill = colorRGB, font = fontPIL) #La couleur est l'argument de la fonction telle qu'elle est
    imgCV = pil2cv(imgPIL) #Sans reconversion des couleurs
    return imgCV           #La valeur de retour est simplement convertie en OpenCV.

# ↓↓↓↓↓↓
#La fonction que vous avez publiée
def cv2_putText_3(img, text, org, fontFace, fontScale, color):
    x, y = org
    imgPIL = Image.fromarray(img)
    draw = ImageDraw.Draw(imgPIL)
    fontPIL = ImageFont.truetype(font = fontFace, size = fontScale)
    w, h = draw.textsize(text, font = fontPIL)
    draw.text(xy = (x,y-h), text = text, fill = color, font = fontPIL)
    return np.array(imgPIL, dtype = np.uint8)

Eh bien, c'est bien simple.

À propos du ROI

ROI (Region of Interest) se traduit par une région d'intérêt et signifie une zone d'intérêt à traiter dans toute l'image. [Cet article](https://qiita.com/mo256man/items/c570c645153cae122196#%E5%BF%9C%E7%94%A8%E4%BE%8B2%E9%A1%94%E6%A4%9C % E5% 87% BA% E3% 81% A8% E9% 80% A3% E5% 8B% 95% E3% 81% 97% E3% 81% 9F% E3% 83% A2% E3% 82% B6% E3 Il est également utilisé dans% 82% A4% E3% 82% AF).

Le commentaire suivant est que si vous obtenez la taille du texte à dessiner à l'avance et PIL seulement la partie de l'image originale où les caractères sont dessinés, ce sera beaucoup plus rapide que PILing l'image originale entière. Je ne pense pas que les gens intelligents soient ... Cependant, vous avez besoin d'un objet ʻImageDraw.Drawpour utiliser ʻImageDraw.textsize ()de PIL. Si vous ne connaissez pas la taille, pouvez-vous créer une image monochrome de base? Ça n'existe pas. Même si le texte est trop petit pour être dessiné, tout ce que vous avez à faire est de préparer l'objet ʻImageDraw.Draw`.

Lisons la quatrième fonction publiée sur cette base.

python


def cv2_putText_4(img, text, org, fontFace, fontScale, color):
    x, y = org
    fontPIL = ImageFont.truetype(font = fontFace, size = fontScale)

    #Taille(0,0)Créer une image solide et créer son objet Draw
    dummy_draw = ImageDraw.Draw(Image.new("RGB", (0,0)))

    #Obtient la largeur et la hauteur du rectangle lors du dessin de la phrase spécifiée avec la taille de police spécifiée
    w, h = dummy_draw.textsize(text, font = fontPIL)
    """
Je l'ajouterai ici plus tard
    """

    #Découpez une partie de l'image originale à la taille que vous venez d'obtenir et ne faites que cette partie en image PIL
    imgPIL = Image.fromarray(img[y-h:y,x:x+w,:])

    #Créez-lui un objet Draw
    draw = ImageDraw.Draw(imgPIL)

    #La norme est qu'il a été coupé pour dessiner à nouveau les caractères.(0,0)
    draw.text(xy = (0,0), text = text, fill = color, font = fontPIL)

    #Rognage inversé Remplacez la partie pertinente de l'image d'origine par une image illustrée par un personnage
    img[y-h:y,x:x+w,:] = np.array(imgPIL, dtype = np.uint8)

    return img

Hé, je vois. Cependant, à cause d'un bug dans ʻImageDraw.textsize () `, si vous utilisez la hauteur obtenue telle quelle, le fond sera un peu coupé. cv_jp_font_test_4.png

Alors que feriez-vous? Nous devrions étendre ce retour sur investissement à texto. Comme ça.

python


    b = int(0.1 * h) #Réglez la hauteur à sécuriser sous la ligne de base de manière appropriée
    
    # imgPIL = Image.fromarray(img[y-h:y,x:x+w,:]) #À
    imgPIL = Image.fromarray(img[y-h:y+b,x:x+w,:]) #À

Correspondance avec la représentation en dehors de l'image

Puisque l'image OpenCV est stockée sous la forme numpy.ndarray, vous pouvez extraire une partie en la rognant en écrivant ʻimg [y: y + h, x: x + w] . C'est l'un des faits les plus émouvants que j'ai appris sur OpenCV, et je l'ai écrit encore et encore. Cependant, cela a ses inconvénients. Bien entendu, puisqu'il s'agit d'un tableau, il n'est pas possible de spécifier en dehors de l'image. Bien sûr, les fonctions cv2.putText ()` et de dessin graphique fournies par OpenCV sont conçues pour qu'il n'y ait aucun problème même si elles s'étendent en dehors de l'image d'origine, mais ma fonction est une erreur à ce stade. Je dois bien le couper.

Je n'ai pas écrit sur le rognage PIL dans mon article, mais contrairement à OpenCV, vous pouvez spécifier en dehors de la plage d'image. https://note.nkmk.me/python-pillow-image-crop-trimming/ Cependant, il m'a montré comment spécifier le retour sur investissement dans le commentaire, et il semble que rendre l'image entière PIL pour le rognage semble aller à l'encontre de l'idée jusqu'à présent, et j'étudiais OpenCV en premier lieu. Je ne veux pas utiliser le rognage PIL.

Et cette façon de penser?

  1. Obtenez la taille de la description du texte à l'avance et créez une image monochrome de cette taille ①
  2. Vérifiez s'il dépasse lorsqu'il est placé dans la position spécifiée sur l'image d'origine Ne rien faire si la zone de texte est complètement hors de l'image
  3. Remplacez tout ou partie de l'image monochromatique par l'image d'origine ②
  4. Dessinez du texte avec PIL La luminosité de R et la luminosité de B sont interchangées, mais il n'y a pas de problème si vous ne l'affichez pas ③
  5. Revenir à OpenCV ④
  6. Coupez uniquement la zone où se trouvait l'image d'origine ⑤
  7. Mettez à jour la cellule correspondante de l'image originale avec celle avec les caractères dessinés 6
1.png 2.png 3.png 4.png 5.png
cv_jp_font_test_7.png

Fonctions supplémentaires

Rendre possible le dessin en spécifiant les coordonnées du haut à droite et du centre ainsi que du bas à gauche

Le but depuis le début était de pouvoir gérer les polices japonaises de la même manière que cv2.putText (), mais je voulais ajouter une autre fonction. Les coordonnées spécifiées par * org * dans cv2.putText () sont en bas à gauche du caractère. Comme je l'ai écrit la dernière fois, je pense que c'est difficile à utiliser. Par conséquent, il est maintenant possible de spécifier si les coordonnées spécifiées par * org * sont en bas à gauche comme dans cv2.putText (), en haut à gauche comme dans PIL ou au centre.

L'image ci-dessous montre que la même coordonnée y est spécifiée dans la fonction d'origine (la coordonnée y de cv2.MARKER_STAR est la même), mais elle est dessinée à des hauteurs différentes en raison de paramètres différents. .. De la gauche, le même standard inférieur gauche que cv2.putText (), le standard supérieur droit équivalent à ʻImageDraw.text () `de PIL et le standard central. cv_jp_font_test_8.png

Inclinez les lettres pour décrire

S'il vous plait, laissez-moi partir.

Une fonction qui intègre ce qui précède

Avec exemple de programme.

python


import numpy as np
import cv2
from PIL import Image, ImageDraw, ImageFont

def cv2_putText_5(img, text, org, fontFace, fontScale, color, mode=0):
# cv2.putText()Argument d'origine "mode" qui n'est pas dans le standard de coordonnées spécifié par org
#0 (par défaut) = cv2.putText()Identique au coin inférieur gauche 1 = supérieur gauche 2 = centre

    #Obtenir la zone de description textuelle
    fontPIL = ImageFont.truetype(font = fontFace, size = fontScale)
    dummy_draw = ImageDraw.Draw(Image.new("RGB", (0,0)))
    text_w, text_h = dummy_draw.textsize(text, font=fontPIL)
    text_b = int(0.1 * text_h) #Contre-mesures pour le montant qui dépasse ci-dessous en raison d'un bug

    #Obtenez les coordonnées en haut à gauche de la zone de description du texte (à partir du coin supérieur gauche de l'image d'origine)
    x, y = org
    offset_x = [0, 0, text_w//2]
    offset_y = [text_h, 0, (text_h+text_b)//2]
    x0 = x - offset_x[mode]
    y0 = y - offset_y[mode]
    img_h, img_w = img.shape[:2]

    #Ne rien faire en dehors de l'écran
    if not ((-text_w < x0 < img_w) and (-text_b-text_h < y0 < img_h)) :
        print ("out of bounds")
        return img

    #En haut à gauche et en bas à droite de la zone où se trouve l'image d'origine dans la zone de description textuelle (le coin supérieur gauche de l'image d'origine est l'origine)
    x1, y1 = max(x0, 0), max(y0, 0)
    x2, y2 = min(x0+text_w, img_w), min(y0+text_h+text_b, img_h)

    #Créez une image noire de la même taille que la zone de description de texte et collez l'image d'origine sur tout ou partie de celle-ci
    text_area = np.full((text_h+text_b,text_w,3), (0,0,0), dtype=np.uint8)
    text_area[y1-y0:y2-y0, x1-x0:x2-x0] = img[y1:y2, x1:x2]

    #Convertissez-le en PIL, spécifiez la police et dessinez le texte (pas de conversion de couleur)
    imgPIL = Image.fromarray(text_area)
    draw = ImageDraw.Draw(imgPIL)
    draw.text(xy = (0, 0), text = text, fill = color, font = fontPIL)

    #Convertir l'image PIL en image OpenCV (pas de conversion de couleur)
    text_area = np.array(imgPIL, dtype = np.uint8)

    #Mettez à jour la zone correspondante de l'image originale avec celle avec les caractères dessinés
    img[y1:y2, x1:x2] = text_area[y1-y0:y2-y0, x1-x0:x2-x0]

    return img

def main():
    img = np.full((200,400,3), (160,160,160), dtype=np.uint8)
    imgH, imgW = img.shape[:2]

    fontPIL = "Dflgs9.TTC" #DF Reiga Song
    size = 30
    text = "Japonais aussi\n possible"
    color = (255,0,0)

    positions = [(-imgW,-imgH),                 #Ceci est en dehors de l'image et n'est pas représenté
                 (0,0), (0,imgH//2), (0,imgH),
                 (imgW//2,0), (imgW//2,imgH//2), (imgW//2,imgH),
                 (imgW,0), (imgW,imgH//2), (imgW,imgH)]

    for pos in positions:
        img = cv2.circle(img, pos, 60, (0,0,255), 3)
        img = cv2_putText_5(img = img,
                            text = text,
                            org = pos,          #J'ai spécifié les mêmes coordonnées que le centre du cercle
                            fontFace = fontPIL,
                            fontScale = size,
                            color = color,
                            mode = 2)           #Les coordonnées que vous venez de spécifier sont le centre de la zone de description des caractères.

    cv2.imshow("", img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()

Quoi que je fasse par moi-même, le standard central est compatible avec cv2.circle () et semble être utile. cv_jp_font_test_9.png

À la fin

Nous tenons à remercier kounoike pour ses commentaires dans l'article précédent et Qiita pour cette opportunité.

Recommended Posts

Rendre la fonction de dessin de polices japonaises dans OpenCV en général
Créer une fonction pour décrire les polices japonaises avec OpenCV
Récupérer l'appelant d'une fonction en Python
Copiez la liste en Python
Correction des arguments de la fonction utilisée dans map
Rendre la progression de dd visible sur la barre de progression
Dessinez sur Jupyter en utilisant la fonction de tracé des pandas
Comparez les polices de jupyter-themes
Avoir le graphique d'équation de la fonction linéaire dessiné en Python
Trier le tableau de chaînes par ordre de longueur et syllabaire japonais
Rendre Opencv disponible en Python
Précautions lors de la superposition de la fonction de densité de probabilité et de l'histogramme dans matplotlib
Rendre le serveur SIP aussi concis que possible (au milieu de l'explication)
Essayez de transcrire la fonction de masse stochastique de la distribution binomiale en Python
Créez une fonction pour obtenir le contenu de la base de données dans Go
Avertissement de tri dans la fonction pd.concat
Traduction japonaise du manuel e2fsprogs
L'histoire de la participation à AtCoder
Implémentation de la fonction de connexion dans Django
Traduction japonaise du manuel man-db
Rendre matplotlib compatible avec le japonais en 3 minutes
Traduction japonaise du manuel util-linux
Traduction japonaise du manuel iproute2
[OpenCV; Python] Résumé de la fonction findcontours
Rechercher le nom et les données d'une variable libre dans un objet fonction
Rendez-le visible en coupant la ligne coupée par journalctrl
[Comprendre en 3 minutes] Le début de Linux
Vérifiez le comportement du destroyer en Python
L'histoire d'une erreur dans PyOCR
Comparaison des modules de conversion japonais en Python3
Implémenter une partie du processus en C ++
Prenez la somme logique de List en Python (fonction zip)
[Python3] Réécrire l'objet code de la fonction
Le résultat de l'installation de python sur Anaconda
R: Utilisez le japonais au lieu du japonais dans le script
A propos des arguments de la fonction setup de PyCaret
Principes de base pour exécuter NoxPlayer en Python
À propos des polices japonaises de matplotlib (pour Mac)
Différence de sortie de la fonction de fenêtre de longueur paire
À la recherche du FizzBuzz le plus rapide en Python
La barre oblique arrière du clavier japonais est "ro"
Rendre la valeur par défaut de l'argument immuable
Traduction japonaise: PEP 20 - Le Zen de Python
[Linux] Différence d'informations temporelles en fonction de l'ID d'horloge de la fonction clock_gettime ()
Obtenez le résultat du GeoCoding inversé en japonais à l'aide du SDK Java de GoogleMapsAPI.
[AWS] Lançons un test unitaire de la fonction Lambda dans l'environnement local
Définir la limite supérieure du nombre de répétitions de fonctions récursives en Python
Obtenez le cours de l'action d'une entreprise japonaise avec Python et faites un graphique
Comment rendre la largeur de police du notebook jupyter mis dans pyenv égale