[Python] J'ai créé une visionneuse d'images avec une fonction de tri simple.

Contexte

Je dois trier beaucoup d'images au travail. Je pense que si vous utilisez un logiciel libre, vous pouvez trier efficacement, mais malheureusement, nous interdisons le logiciel libre en principe. Ensuite, dans l'esprit de le faire, j'ai créé ** une visionneuse d'images avec une fonction de tri simple **.

Image terminée

Exemple: Une image de chien qui a été confondue dans une image de chat est extraite avec une étiquette "1". 仕分けツールデモ_.gif

  1. Après avoir appuyé sur le bouton "Charger", spécifiez le dossier. Ensuite, toutes les images du répertoire, y compris le sous-répertoire, sont lues.
  2. Envoyez l'image avec "←" et "→" et appuyez sur les touches numériques pour étiqueter le numéro de la touche enfoncée sur l'image affichée. (Étiquetage = la touche saisie (numéro) est affichée sous le nom du fichier. En passant, appuyez sur 0 pour annuler l'étiquetage)
  3. Exécutez la division des dossiers en fonction du contenu étiqueté avec le bouton "Exécution de l'assortiment".
  4. Après cela, vous pouvez ouvrir le dossier des fichiers image chargés en appuyant sur le bouton "Ouvrir le dossier".

Code complet

Cliquez ici pour le corps principal de l'outil de tri ↓

** Outil de tri ** (Cliquez pour voir le code)

Outil de tri.py


from file_walker import folder_walker  #Fonction auto-fabriquée
from folder_selecter import file_selecter  #Fonction auto-fabriquée

import os
import shutil
import subprocess
import tkinter as tk
from tkinter import Label, Tk, StringVar

from PIL import Image, ImageTk  #Bibliothèque externe


#Lecture de fichiers- - - - - - - - - - - - - - - - - - - - - - - -
def load_file(event):

    global img_num, item, dir_name

    #Lisez le dossier
    tex_var.set("Chargement du fichier...")
    dir_name = file_selecter(dir_select=True)
    if not dir_name == None:
        file_list = folder_walker(dir_name)

    #Répertoriez les images pouvant être lues à partir du fichier
    for f in file_list:
        try:
            img_lst.append(Image.open(f))
            filename_lst.append(f)
        except:
            pass

    #Redéfinissez la taille de la toile pour l'adapter à la taille de la fenêtre
    window_resize()

    #Conversion d'image
    for f in img_lst:

        #Redimensionné pour s'adapter à l'intérieur de la toile
        resized_img = img_resize_for_canvas(f, image_canvas)

        #Conversion d'image pour qu'elle puisse être affichée avec tkinter
        tk_img_lst.append(ImageTk.PhotoImage(
            image=resized_img, master=image_canvas))

    #Obtenez le centre de la toile
    c_width_half = round(int(image_canvas["width"]) / 2)
    c_height_half = round(int(image_canvas["height"]) / 2)

    #Affichage sur toile
    img_num = 0
    item = image_canvas.create_image(
        c_width_half, c_height_half,  image=tk_img_lst[0], anchor=tk.CENTER)
    #Réécriture d'étiquettes
    tex_var.set(filename_lst[img_num])

    #Masquer le bouton de chargement
    load_btn.pack_forget()
    #Disposition des boutons d'exécution du tri
    assort_btn.pack()

#Vers l'image suivante- - - - - - - - - - - - - - - - - - - - - - - -
def next_img(event):
    global img_num

    #Obtenez le nombre d'images en cours de chargement
    img_count = len(tk_img_lst)

    #Déterminez si l'image n'est pas la dernière
    if img_num >= img_count - 1:
        pass
    else:
        #Numéro d'image affiché.Mettre à jour et afficher
        img_num += 1
        image_canvas.itemconfig(item, image=tk_img_lst[img_num])
        #Réécriture d'étiquettes
        tex_var.set(filename_lst[img_num])
        #Afficher l'étiquetage
        if filename_lst[img_num] in assort_dict:
            assort_t_var.set(assort_dict[filename_lst[img_num]])
        else:
            assort_t_var.set("")


#Vers l'image précédente- - - - - - - - - - - - - - - - - - - - - - - -
def prev_img(event):
    global img_num

    #Déterminez si l'image n'est pas la première
    if img_num <= 0:
        pass
    else:
        #Numéro d'image affiché.Mettre à jour et afficher
        img_num -= 1
        image_canvas.itemconfig(item, image=tk_img_lst[img_num])
        #Réécriture d'étiquettes
        tex_var.set(filename_lst[img_num])
        #Afficher l'étiquetage
        if filename_lst[img_num] in assort_dict:
            assort_t_var.set(assort_dict[filename_lst[img_num]])
        else:
            assort_t_var.set("")


#Redéfinir la taille de la toile à partir de la taille de la fenêtre- - - - - - - - - - - - - - - - -
def window_resize():

    image_canvas["width"] = image_canvas.winfo_width()
    image_canvas["height"] = image_canvas.winfo_height()


#Réduire l'image pour l'adapter à la taille de la toile- - - - - - - - - - - - - - - - - - - -
def img_resize_for_canvas(img, canvas, expand=False):

    size_retio_w = int(canvas["width"]) / img.width
    size_retio_h = int(canvas["height"]) / img.height

    if expand == True:
        size_retio = min(size_retio_w, size_retio_h)
    else:
        size_retio = min(size_retio_w, size_retio_h, 1)

    resized_img = img.resize((round(img.width * size_retio),
                              round(img.height * size_retio)))
    return resized_img

#Affichage de l'image- - - - - - - - - - - - - - - - - - - - - - - -
def image_show(event):
    img_lst[img_num].show()


#Étiquetage des images- - - - - - - - - - - - - - - - - - - - - - - -
def file_assort(event):

    if str(event.keysym) in ["1", "2", "3", "4", "5", "6", "7", "8", "9"]:
        assort_dict[filename_lst[img_num]] = str(event.keysym)
    elif str(event.keysym) == "0":
        del assort_dict[filename_lst[img_num]]

    #Afficher l'étiquetage
    if filename_lst[img_num] in assort_dict:
        assort_t_var.set(assort_dict[filename_lst[img_num]])
    else:
        assort_t_var.set("")

    print(assort_dict[filename_lst[img_num]])


#Exécution de la division des dossiers- - - - - - - - - - - - - - - - - - - - - - - -
def assort_go(event):

    global f_dir

    for f in assort_dict:
        #Obtenez le nom du fichier / nom du dossier avant et après le tri
        f_dir = os.path.dirname(f)
        f_basename = os.path.basename(f)
        new_dir = os.path.join(f_dir, assort_dict[f])
        new_path = os.path.join(new_dir, f_basename)

        #Vérification de l'existence du répertoire
        if not os.path.exists(new_dir):
            os.mkdir(new_dir)
        #Déplacer le fichier
        shutil.move(f, new_path)
        #Afficher / masquer divers boutons
        assort_btn.pack_forget()
        open_folder_btn.pack()

        print(new_path)

#Dossier ouvert- - - - - - - - - - - - - - - - - - - - - - - -
def folder_open(event):
    #Convertir le chemin pour ouvrir dans l'Explorateur
    open_dir_name = f_dir.replace("/", "\\")
    #Ouvrir dans l'explorateur
    subprocess.Popen(['explorer', open_dir_name])
    #Fermez la fenêtre tkinter
    root.destroy()
 
    print(open_dir_name)


#Traitement principal-------------------------------------------------------
if __name__ == "__main__":

    #Variables globales
    img_lst, tk_img_lst = [], []
    filename_lst = []
    assort_file_list = []
    assort_dict = {}

    img_num = 0
    f_basename = ""

    #paramètres de dessin tkinter
    root = tk.Tk()

    root.title(u"Outil d'affichage / tri")
    root.option_add("*font", ("Meiryo UI", 11))

    #Charger les paramètres de dessin du bouton
    load_btn = tk.Button(root, text="Lis")
    load_btn.bind("<Button-1>", load_file)
    load_btn.pack()

    #Paramètres de dessin de canevas
    image_canvas = tk.Canvas(root,
                             width=640,
                             height=480)

    image_canvas.pack(expand=True, fill="both")

    #Affichage des résultats de tri
    assort_t_var = tk.StringVar()
    assort_label = tk.Label(
        root, textvariable=assort_t_var, font=("Meiryo UI", 14))
    assort_label.pack()

    #Paramètres de dessin d'étiquette de nom de fichier
    tex_var = tk.StringVar()
    tex_var.set("nom de fichier")

    lbl = tk.Label(root, textvariable=tex_var, font=("Meiryo UI", 8))
    lbl["foreground"] = "gray"
    lbl.pack()

    #Réglage de fonctionnement pour alimenter les images avec les touches droite et gauche
    root.bind("<Key-Right>", next_img)
    root.bind("<Key-Left>", prev_img)
    # 「Ctrl」+Affichage de l'image avec "P"
    root.bind("<Control-Key-p>", image_show)

    #Tri des paramètres cibles avec les touches numériques
    root.bind("<Key>", file_assort)

    #Bouton d'exécution de tri
    assort_btn = tk.Button(root, text="Exécution du tri")
    assort_btn.bind("<Button-1>", assort_go)

    #Bouton pour ouvrir le dossier
    open_folder_btn = tk.Button(root,text="Dossier ouvert")
    open_folder_btn.bind("<Button-1>", folder_open)

    root.mainloop()

La fonction d'auto-création créée a été utilisée pour l'opération de spécification du dossier et l'opération d'acquisition de la liste de fichiers à partir du dossier spécifié. Stockez le fichier .py suivant dans le même dossier que l'outil de tri ci-dessus .py.

** file_selector.py ** (cliquez pour voir le code)

file_selector.py


import os
import sys

import tkinter as f_tk
from tkinter import filedialog

def file_selecter(ini_folder_path = str(os.path.dirname(sys.argv[0])), 
                                    multiple= False, dir_select = False):
    """
Ouvrez une boîte de dialogue et sélectionnez un fichier ou un dossier.
Si vous ne spécifiez pas le dossier initial, le dossier du fichier lui-même est ouvert.
Vous pouvez sélectionner la sélection de dossier et la sélection de fichier (multiple / unique) comme options.

    Parameters
    ----------
    ini_folder_path : str
Le dossier à ouvrir initialement. La valeur par défaut est le chemin du dossier du fichier exécutable
    multiple : bool
S'il faut autoriser la sélection de plusieurs fichiers. La valeur par défaut est False, qui est une sélection unique.
    dir_select : bool
Mode de sélection de dossier. La valeur par défaut est False, qui est en mode de sélection de fichier.
    """

    root_fileselect=f_tk.Tk()
    root_fileselect.withdraw()  #Masquer la fenêtre

    if  os.path.isfile(ini_folder_path):
        ini_folder_path = os.path.dirname(ini_folder_path) #Si le nom de fichier est inclus dans la spécification de dossier initiale, le dossier du fichier est renvoyé.

    if dir_select:
        select_item = f_tk.filedialog.askdirectory(initialdir=ini_folder_path)  #Mode de sélection de répertoire

    elif multiple:
        select_item = f_tk.filedialog.askopenfilenames(initialdir=ini_folder_path)  #Mode de sélection de fichier (multiple)
    else:
        select_item = f_tk.filedialog.askopenfilename(initialdir=ini_folder_path)  #Mode de sélection de fichier (unique)

    root_fileselect.destroy()

    if not select_item =="":
        return select_item

** file_walker.py ** (cliquez pour voir le code)

file_walker.py


import os
import pathlib

def folder_walker(folder_path, recursive = True, file_ext = ".*"):
    """
Obtenez une liste de fichiers dans le dossier spécifié.
Il peut être obtenu de manière récursive ou non récursive en spécifiant un argument.

    Parameters
    ----------
    folder_path : str
Chemin du dossier cible
    recursive : bool
S'il faut l'obtenir récursivement. La valeur par défaut est True, qui est acquise de manière récursive.
    file_ext : str
Spécifiez l'extension du fichier à lire. Exemple:".jpg "Besoin d'une période comme. La valeur par défaut est".*"Non spécifié dans
    """

    p = pathlib.Path(folder_path)

    if recursive:
        return list(p.glob("**/*" + file_ext))  # **/*Obtenez des fichiers de manière récursive avec
    else:
        return list(p.glob("*" + file_ext))  #Ne pas récupérer les fichiers de manière récursive

Ce que j'ai étudié

Relation parent-enfant de la fenêtre Tkinter

J'utilise également le fileialog de Tkinter pour sélectionner le dossier, mais au début, j'ai essayé de dessiner la racine de la fenêtre principale après avoir sélectionné le dossier. Ensuite, j'ai eu du mal car la racine principale ne s'affichait pas correctement ou devenait inactive, probablement à cause de l'héritage. Après tout, si j'appelais fileialog après avoir dessiné la racine principale, je pourrais dessiner la racine sans problème.

Le concept de ramassage des ordures

Au début, j'étais impatient car le contenu du canevas et l'étiquette avec tk.StringVar () intégré n'étaient pas affichés du tout. À la suite de diverses enquêtes, python a un concept appelé garbage collection, et le contenu des variables jugées inutiles (= inaccessibles) est automatiquement supprimé. Par conséquent, lorsque le canevas a tenté de faire référence à l'objet, il n'a pas pu être dessiné car son contenu était perdu. Pour éviter cela, vous pouvez soit le charger en tant que variable globale à l'avance, soit demander à une instance de la classe de charger l'objet. Voir ci-dessous pour plus de détails. Page de référence: [Python] Que faire lorsque des objets tels que des images ne peuvent pas être dessinés avec Tkinter https://daeudaeu.com/create_image_problem/

Reportez-vous aux paramètres du widget tkinter

J'ai eu du mal à trouver un moyen de référencer les paramètres du widget. Après tout, tout ce que j'avais à faire était de l'obtenir avec ** nom du widget ["nom du paramètre"] ** comme indiqué ci-dessous.


canvas_w = int(image_canvas["width"])

(Dans ce cas, étant donné que la taille de la fenêtre a été rendue variable, il était nécessaire d'obtenir la taille du canevas afin que le centre du canevas puisse être déterminé.)

Impressions / avenir

Je pensais que ce serait facile à faire car c'était une opération simple, mais j'ai eu du mal à l'improviste en raison d'un manque de connaissances. À l'avenir, j'aimerais apporter diverses modifications basées sur celle créée cette fois pour en faire un outil plus convivial. En particulier,

etc.

Recommended Posts

[Python] J'ai créé une visionneuse d'images avec une fonction de tri simple.
J'ai fait un simple blackjack avec Python
J'ai fait une loterie avec Python.
J'ai créé un démon avec Python
J'ai fait un jeu de frappe simple avec tkinter de Python
J'ai créé une application de livre simple avec python + Flask ~ Introduction ~
J'ai fait un compteur de caractères avec Python
J'ai fait une carte hexadécimale avec Python
J'ai fait un jeu rogue-like avec Python
J'ai créé un fichier de configuration avec Python
J'ai fait un simulateur de neurones avec Python
J'ai fait un circuit simple avec Python (AND, OR, NOR, etc.)
J'ai essayé de créer une fonction de similitude d'image avec Python + OpenCV
J'ai fait une prévision météo de type bot avec Python.
J'ai créé une application graphique avec Python + PyQt5
J'ai essayé de créer un bloqueur de filles pourries sur Twitter avec Python ①
[Python] J'ai créé un téléchargeur Youtube avec Tkinter.
J'ai fait un simple portefeuille de Bitcoin avec pycoin
J'ai fait un jeu de cueillette avec Python
Made Mattermost Bot avec Python (+ Flask)
J'ai fait un Twitter BOT avec GAE (python) (avec une référence)
J'ai fait un jeu d'éclairage de sapin de Noël avec Python
J'ai fait un blackjack avec du python!
J'ai créé une application de notification de nouvelles en ligne avec Python
Tri des fichiers image avec Python (2)
Tri des fichiers image avec Python
J'ai essayé de faire LINE BOT avec Python et Heroku
J'ai fait un texte Python
J'ai fait un blackjack avec Python.
J'ai créé wordcloud avec Python.
J'ai créé un package pour filtrer les séries chronologiques avec python
J'ai fait un jeu de puzzle (comme) avec Tkinter of Python
Découpez une image avec python
J'ai fait un Line-bot avec Python!
J'ai envoyé un SMS avec Python
[Python] J'ai créé une fonction qui déchiffre et décrypte AES simplement en le lançant avec pycrypto.
J'ai créé une fonction pour découper l'image de python openCV, alors veuillez l'utiliser.
J'ai créé une bibliothèque qui lit facilement les fichiers de configuration avec Python
J'ai fait un package qui peut comparer des analyseurs morphologiques avec Python
Essayez d'extraire une chaîne de caractères d'une image avec Python3
J'ai créé un formulaire de tweet Nyanko avec Python, Flask et Heroku
J'ai créé beaucoup de fichiers pour la connexion RDP avec Python
J'ai fait un shuffle qui peut être réinitialisé (inversé) avec Python
J'ai créé un chat-holdem de serveur de jeu de poker en utilisant websocket avec python
J'ai créé une bibliothèque de wrapper Python pour l'API de reconnaissance d'images docomo.
Créer un décorateur de fonction Python avec Class
Créer un fichier power simple avec Python
Lecteur RSS simple réalisé avec Django
Créer une visionneuse de traitement d'image avec PySimpleGUI
J'ai fait un programme de gestion de la paie en Python!
Client API Slack simple réalisé avec Python
Créez une image factice avec Python + PIL.
J'ai dessiné une carte thermique avec Seaborn [Python]
J'ai essayé un langage fonctionnel avec Python
Ce que j'ai fait avec les tableaux Python
J'ai fait un jeu de vie avec Numpy