Python est de plus en plus populaire avec la popularité de l'IA (Deep Learning). J'ai créé des applications GUI pour afficher, dessiner et enregistrer des images avec Python, mais j'ai essayé d'organiser le code et ainsi de suite. J'écrirai le mécanisme pour organiser ma compréhension. J'ai utilisé une bibliothèque appelée Qt pour Python (PySide2). Développé à l'origine en C ++, Qt vous permet de développer des applications multiplateformes qui s'exécutent sur différents systèmes d'exploitation tels que Windows, Mac et Linux à partir du même code source. Il existe Qt pour Python ou PyQt pour utiliser Qt depuis Python, mais j'ai utilisé Qt pour Python, qui n'est pas si lié par la licence de l'application créée. Il est facile de comprendre si vous comprenez l'orientation des objets lors de la création d'applications GUI. Lors du dessin d'une image, de nombreux objets apparaissent, et il est difficile pour les débutants de comprendre les rôles et les relations de chacun, mais il sera plus facile de comprendre si quelque chose qui bouge même s'il n'est pas dans une belle forme est créé. (Histoire d'expérience) Donc, si vous avez une image que vous voulez faire, n'abandonnez pas et essayez diverses choses. J'espère que cet article vous aidera à ce moment-là.
L'image globale de l'application est présentée ci-dessous. La configuration de l'écran est gérée par celui qui a une fonction appelée Disposition qui organise automatiquement les parties placées (Widgets) en fonction de la taille de la fenêtre.
QVBoxLayout () s'aligne verticalement et QHBoxLayout () s'aligne horizontalement. Utilisez QFormLayout () lorsque vous souhaitez créer une paire comme un nom et sa valeur. L'exemple d'utilisation est le suivant.
self.main_layout = QVBoxLayout()
#Définir la zone d'affichage de l'image
self.graphics_view = QGraphicsView()
self.upper_layout.addWidget(self.graphics_view)
#Imbriquez la disposition en haut de l'écran dans la disposition principale
self.upper_layout = QHBoxLayout()
self.main_layout.addLayout(self.upper_layout)
Créez une classe MainWindow (QMainWIndow) qui hérite de QMainWindow comme fenêtre principale. Placez-y des pièces (widgets) avec diverses fonctions et décrivez l'opération lorsque vous appuyez dessus. J'ai dû déclarer self.mainWidget = QWidget (self) dans l'initialisation ici avant de pouvoir définir Layout.
Le code de lancement de l'application ressemble à ceci:
class MainWindow(QMainWindow):
def __init__(self):
#Ci-dessous, divers processus sont décrits
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Déclarez-le dans le constructeur (def init ()) pour créer une barre de menu (sélection en haut de l'application) dans la fenêtre principale. Pour créer une barre de menus et y créer un menu appelé «Fichier», procédez comme suit.
class MainWindow(QMainWindow):
def __init__(self):
self.main_menu = self.menuBar()
self.file_menu = self.main_menu.addMenu('File')
Si vous souhaitez créer un élément appelé "Fichier Ouvrir" dans le menu "Fichier", procédez comme suit.
# Set "Original Image Open" menu
self.org_img_open_button = QAction(self.style().standardIcon(getattr(QStyle, 'SP_FileDialogStart')), 'Open Orginal Image', self)
self.org_img_open_button.setShortcut('Ctrl+O')
self.org_img_open_button.triggered.connect(self.open_org_img_dialog)
self.file_menu.addAction(self.org_img_open_button)
Dans la première ligne, il est défini comme QAction () qui fonctionne lorsque cet élément est sélectionné, l'icône est sélectionnée parmi celles standard et le nom affiché est «Ouvrir l'image originale». La deuxième ligne est définie de manière à ce que cet élément puisse être sélectionné avec la touche de raccourci Ctrl + O. La troisième ligne est connectée à la fonction qui définit le comportement lorsque cet élément est sélectionné. Ceci est expliqué dans la section suivante. Cet élément est enregistré dans file_menu sur la 4ème ligne.
Chez Qt, les opérations de l'utilisateur et les réactions informatiques correspondantes sont effectuées par des méthodes appelées Signal et Slot. Un signal est émis lorsque l'utilisateur effectue une opération telle qu'un bouton de menu ou en faisant glisser la souris. Ensuite, effectuez le traitement correspondant dans le Slot défini pour recevoir chaque signal. Plus précisément, l'opération lorsqu'un élément de la barre de menus est sélectionné est écrite comme suit.
#barre de menu->Fichier-> 'Original Image Open'Quand est sélectionné
#Envoyez un signal déclenché. Le signal est ouvert_org_img_Connecté à la fonction de dialogue.
self.org_img_open_button = QAction(self.style().standardIcon(getattr(QStyle, 'SP_FileDialogStart')), 'Open Orginal Image', self)
self.org_img_open_button.triggered.connect(self.open_org_img_dialog)
#barre de menu->Fichier-> 'Original Image Open'Une fonction qui devient un slot qui reçoit le signal transmis lorsque est sélectionné
def open_org_img_dialog(self):
options = QFileDialog.Options()
org_img_default_path = self.app_setting["SoftwareSetting"]["file_path"]["org_img_dir"]
self.org_img_file_path, selected_filter = QFileDialog.getOpenFileName(self, 'Select original image', org_img_default_path, 'Image files(*.jpg *jpeg *.png)', options=options)
org_img_dir_path, org_img_file = os.path.split(self.org_img_file_path)
org_img_bare_name, org_img_ext = os.path.splitext(org_img_file)
self.org_img_path_label.setText(self.org_img_file_path)
Lorsqu'une couleur est sélectionnée en cliquant avec la souris dans la barre de couleurs décrite plus loin, la couleur sélectionnée est dessinée sous la forme d'un carré dans la zone de dessin pour l'affichage afin de faciliter la compréhension de l'utilisateur. À ce stade, il devient nécessaire d'agir entre la barre de couleur et l'objet dans la zone de dessin de couleur sélectionnée. À ce moment, il devient nécessaire de connecter Signal et Slot via la fenêtre principale parente. Dans ce cas, la classe qui gère les éléments de dessin à placer dans la zone de dessin de couleur sélectionnée définit son propre signal et émet le signal avec les informations de couleur de la pièce cliquée. Plus précisément, le code ressemble à celui ci-dessous.
# Class for graphics contents of tools on main window
class GraphicsSceneForTools(QGraphicsScene):
# Define custom signal
img_info = Signal(QColor)
def mousePressEvent(self, event):
# For check program action
pos = event.scenePos()
x = pos.x()
y = pos.y()
#Si le curseur ou le stylo est sélectionné dans la barre d'outils d'édition d'image, les informations de couleur de l'emplacement cliqué(QColor)Émettre un signal avec
if self.mode == 'cursor' or self.mode == 'pen':
self.pix_rgb = self.img_content.pixelColor(x, y)
self.img_info.emit(self.pix_rgb)
#Définissez le Slot correspondant au Signal des informations de couleur sélectionnées sur le côté de la fenêtre principale
class MainWindow(QMainWindow):
#Objet de gestion des éléments de dessin pour la barre de couleurs
self.color_bar_scene = GraphicsSceneForTools()
#Connectez-vous avec une fonction qui devient un slot qui reçoit le signal qui a envoyé les informations de couleur
self.color_bar_scene.img_info.connect(self.set_selected_color)
Plusieurs widgets sont liés pour l'affichage de l'image. La relation est illustrée dans la figure ci-dessous. Préparez QGraphicsView, qui est un objet de zone de dessin, dans MainWindow, placez QGraphicsScene qui contient et gérez les objets de dessin, et ajoutez des dessins et des images tels que des lignes et des cercles à QGraphicsScene. Aller.
L'ensemble QGraphicsScene dans la zone de dessin principale affiche les informations de pixel de l'image affichée dans la barre d'état lorsque l'outil curseur est sélectionné et le calque au-dessus de l'image lorsque l'outil crayon ou gomme est sélectionné. J'essaierai de dessiner. Afin d'ajouter une telle fonction définie par vous-même, créez une scène graphique qui hérite de QGraphic Scene comme suit. En définissant la zone de dessin parent QGraphicsView et sa MainWindow parent dans la fonction d'initialisation d'initialisation, les informations obtenues à partir de chaque élément de ce GraphicsScene peuvent être transmises à la zone ou à la fenêtre de dessin.
Pour être honnête, je ne suis pas sûr de QGraphicsView et QGraphicsScene au début, mais je pensais que c'était compliqué et gênant d'accéder et de contrôler le contenu! Il semble que ce soit parce qu'il est conçu pour répondre à la demande complexe de dessiner dans la plage visible (plage de dessin) à partir de différents points de vue, même si le contenu cible à dessiner ne change pas. Par exemple, lorsque le contenu à dessiner est plus grand que la zone de dessin, il peut être affiché lors du changement de point de vue avec la barre de défilement, ou un objet 3D peut être affiché lors du changement de point de vue.
class GraphicsSceneForMainView(QGraphicsScene):
def __init__(self, parent=None, window=None, mode='cursor'):
QGraphicsScene.__init__(self, parent)
# Set parent view area
self.parent = parent
# Set grand parent window
self.window = window
# Set action mode
self.mode = mode
# mouse move pixels
self.points = []
# added line items
self.line_items = []
self.lines = []
# added line's pen attribute
self.pens = []
def set_mode(self, mode):
self.mode = mode
def set_img_contents(self, img_contents):
# image data of Graphics Scene's contents
self.img_contents = img_contents
def clear_contents(self):
self.points.clear()
self.line_items.clear()
self.lines.clear()
self.pens.clear()
self.img_contents = None
def mousePressEvent(self, event):
# For check program action
pos = event.scenePos()
x = pos.x()
y = pos.y()
if self.mode == 'cursor':
# Get items on cursor
message = '(x, y)=({x}, {y}) '.format(x=int(x), y=int(y))
for img in self.img_contents:
# Get pixel value
pix_val = img.pixel(x, y)
pix_rgb = QColor(pix_val).getRgb()
message += '(R, G, B) = {RGB} '.format(RGB=pix_rgb[:3])
# show scene status on parent's widgets status bar
self.window.statusBar().showMessage(message)
Lien vers la documentation QGraphicsView Lien vers la documentation QGraphicsScene
Pour placer une image dans QGraphicsScene, utilisez QGraphicsScene.addItem (QPixmap) au format QPixmap. Cependant, dans le format QPixmap, les informations de chaque pixel ne peuvent pas être acquises ou réécrites, conservez-les donc au format QImage et convertissez-les en QPixmap pour le dessin. Pour créer une QImage à partir d'un fichier image, le transformer en QPixmap, et l'ajouter à QGraphicsScene, le code ressemble à ceci:
#self fait référence à MainWindow
self.scene = GraphicsSceneForMainView(self.graphics_view, self)
self.org_qimg = QImage(self.org_img_file_path)
self.org_pixmap = QPixmap.fromImage(self.org_qimg)
scene.addItem(self.org_pixmap)
Pour créer une QImage de 8 bits (256 gradations) RGBA (A est la transparence) dans le ciel, utilisez le code suivant.
self.layer_qimg = QImage(self.org_img_width, self.org_img_height, QImage.Format_RGBA8888)
Lien vers la documentation QImage Lien vers la documentation QPixmap
Cet article est utilisé comme référence, et la barre de couleur pour sélectionner la couleur du stylo est mappée à chaud (lisse du bleu basse température au rouge haute température). Je l'ai fait avec (change). L'objet qui définit la barre de couleur est en partie décrit dans l'explication de «Mécanisme de réception d'actions sur les pièces», mais j'ai créé une classe appelée GraphicsSceneForTools qui a hérité de QGraphicsScene et l'a utilisée. Ce faisant, en cliquant dessus avec la souris, un signal sera émis en fonction de la position pressée à partir de cet objet, et dans MainWindow (pour être exact, MainWindow-> QGraphicsView-> GraphicsSceneForTools) de l'objet parent où l'objet est placé. En préparant une fonction Slot à recevoir par Signal, la zone d'affichage couleur sélectionnée est remplie avec la couleur sélectionnée par l'utilisateur à partir de la barre de couleur et affichée d'une manière facile à comprendre. Dans la classe GraphicsSceneForTools (QGraphicsScene) nouvellement préparée, un signal avec QColor (informations de couleur) est préparé comme img_info = Signal (QColor), et il est défini lorsque la souris est cliquée dans def mousePressEvent (self, event). J'essaie de produire un signal de signal avec la couleur (self.pix_rgb) de la position des coordonnées cliquées de l'élément de dessin (barre de couleur dans ce cas) comme self.img_info.emit (self.pix_rgb). Du côté de MainWindow, set_selected_color () est préparé comme la fonction Slot du côté réception lorsque l'objet GraphicsSceneForTools émet le signal correspondant en tant que self.color_bar_scene.img_info.connect (self.set_selected_color). Plus précisément, le code est le suivant.
class MainWindow(QMainWindow):
# Set color bar
self.color_bar_width = 64
self.color_bar_height = 256
self.color_bar_view = QGraphicsView()
self.color_bar_view.setFixedSize(self.color_bar_width+3, self.color_bar_height+3)
self.color_bar_scene = GraphicsSceneForTools()
#Définissez la barre de couleurs.
#Les données de changement de couleur qui sont à la base de la barre de couleur sont auto.colormap_C'est dans les données.
#Veuillez vous référer au code source ou à l'article de référence pour savoir comment le créer.
self.color_bar_img = QImage(self.color_bar_width, self.color_bar_height, QImage.Format_RGB888)
for i in range(self.color_bar_height):
# Set drawing pen for colormap
ii = round(i * (1000/256))
color = QColor(self.colormap_data[ii][0], self.colormap_data[ii][1], self.colormap_data[ii][2])
pen = QPen(color, 1, Qt.SolidLine, \
Qt.SquareCap, Qt.RoundJoin)
self.color_bar_scene.addLine(0, self.color_bar_height - i-1, self.color_bar_width, self.color_bar_height - i-1, pen=pen)
for j in range(self.color_bar_width):
self.color_bar_img.setPixelColor(j, self.color_bar_height-i-1, color)
self.color_bar_scene.set_img_content(self.color_bar_img)
self.color_bar_view.setScene(self.color_bar_scene)
# Connect signal to slot of color_bar_scene
self.color_bar_scene.img_info.connect(self.set_selected_color)
# Slot of color bar clicked for selection color
def set_selected_color(self, color):
# Delete existng image item
self.select_color_scene.removeItem(self.select_color_rect)
self.draw_color = color
brush = QBrush(self.draw_color)
self.select_color_rect = self.select_color_scene.addRect(QRect(0, 0, self.select_color_view_size, self.select_color_view_size), \
brush=brush)
self.select_color_view.setScene(self.select_color_scene)
# Class for graphics contents of tools on main window
class GraphicsSceneForTools(QGraphicsScene):
# Define custom signal
img_info = Signal(QColor)
def __init__(self, parent=None, window=None):
QGraphicsScene.__init__(self, parent)
# Set parent view area
self.parent = parent
# Set grand parent window
self.window = window
self.mode = 'cursor'
def set_mode(self, mode):
self.mode = mode
def set_img_content(self, img_content):
# image data of Graphics Scene's contents
self.img_content = img_content
def mousePressEvent(self, event):
# For check program action
pos = event.scenePos()
x = pos.x()
y = pos.y()
if self.mode == 'cursor' or self.mode == 'pen':
self.pix_rgb = self.img_content.pixelColor(x, y)
self.img_info.emit(self.pix_rgb)
Avec le contenu ci-dessus, vous pourrez sélectionner un stylo ou une gomme à dessiner. L'image originale est affichée et le résultat de mon dessin est dessiné sur un autre calque au-dessus. De plus, pour enregistrer le contenu que vous avez dessiné, vous devez exporter le contenu dessiné en faisant glisser la souris sous forme d'image. Le contenu dessiné par le glissement de la souris de l'utilisateur est enregistré en tant que collection de lignes qui sont des loci. Les lignes ont des points de début et de fin et des attributs de plume (couleur, taille). Par conséquent, les coordonnées qui passent sur l'image sont calculées en fonction du point de départ et du point final dans l'ordre à partir du groupe de lignes, et sont reflétées dans l'image pour l'exportation.
#Une classe qui gère les éléments à placer dans la zone de dessin
class GraphicsSceneForMainView(QGraphicsScene):
#Lorsqu'un stylo ou une gomme est sélectionné et dessiné en faisant glisser la souris
def mouseMoveEvent(self, event):
# For check program action
pos = event.scenePos()
x = pos.x()
y = pos.y()
if self.mode == 'pen' or self.mode == 'eraser':
if x >= 0 and x < self.width() and y >= 0 and y < self.height():
if len(self.points) != 0:
draw_color = self.window.draw_color
# Set transparenc value
draw_color.setAlpha(self.window.layer_alpha)
draw_size = self.window.draw_tool_size
pen = QPen(draw_color, draw_size, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
self.lines_items.append(self.addLine(QLineF(self.points[-1].x(), self.points[-1].y(), x, y), pen=pen))
#La position de la ligne dessinée et les attributs du stylo à ce moment-là pour un enregistrement ultérieur en tant qu'image(Couleur, taille)sauvegarder
self.lines.append(self.lines_items[-1].line())
self.pens.append(pen)
self.points.append(pos)
# Main Window components
class MainWindow(QMainWindow):
#Traitement pour refléter le dessin de l'utilisateur sur l'image
#Le contenu dessiné en faisant glisser la souris est une collection d'informations de ligne avec un point de départ et un point de fin.
def make_layer_image(self):
for i, line in enumerate(self.scene.lines):
pen = self.scene.pens[i]
pen_size = int(pen.width())
pen_color = pen.color()
# start pixel of line
x1 = int(line.x1())
y1 = int(line.y1())
# end pixel of line
x2 = int(line.x2())
y2 = int(line.y2())
dx = int(line.dx())
dy = int(line.dy())
# When only 1pixl line
if dx <= 1 and dy <= 1:
draw_pix_x1_s = max(x1 - int(pen_size/2), 0)
draw_pix_x1_e = min(x1 + int(pen_size/2), self.org_img_width-1)
draw_pix_y1_s = max(y1 - int(pen_size/2), 0)
draw_pix_y1_e = min(y1 + int(pen_size/2), self.org_img_height-1)
# for Pen's size
for y in range(draw_pix_y1_s, draw_pix_y1_e):
for x in range(draw_pix_x1_s, draw_pix_x1_e):
self.layer_qimg.setPixelColor(x, y, pen_color)
draw_pix_x2_s = max(x2 - int(pen_size/2), 0)
draw_pix_x2_e = min(x2 + int(pen_size/2), self.org_img_width-1)
draw_pix_y2_s = max(y2 - int(pen_size/2), 0)
draw_pix_y2_e = min(y2 + int(pen_size/2), self.org_img_height-1)
# for Pen's size
for y in range(draw_pix_y2_s, draw_pix_y2_e):
for x in range(draw_pix_x2_s, draw_pix_x2_e):
self.layer_qimg.setPixelColor(x, y, pen_color)
else:
# For avoid devide by 0
if dx == 0:
for y in range(y1, y2+1):
draw_pix_y_s = y - int(pen_size/2)
draw_pix_y_e = y + int(pen_size/2)
# for Pen's size
for yy in range(draw_pix_y_s, draw_pix_y_e):
self.layer_qimg.setPixelColor(x1, yy, pen_color)
else:
grad = dy/dx
# Choose coordinates with small slope not to skip pixels
if grad >= 1.0:
for x in range(dx):
y = y1 + int(grad * x + 0.5)
draw_pix_x_s = max(x1 + x - int(pen_size/2), 0)
draw_pix_x_e = min(x1 + x + int(pen_size/2), self.org_img_width-1)
draw_pix_y_s = max(y - int(pen_size/2), 0)
draw_pix_y_e = min(y + int(pen_size/2), self.org_img_height-1)
# for Pen's size
for yy in range(draw_pix_y_s, draw_pix_y_e+1):
for xx in range(draw_pix_x_s, draw_pix_x_e+1):
self.layer_qimg.setPixelColor(xx, yy, pen_color)
else:
for y in range(dy):
x = x1 + int(1/grad * y + 0.5)
draw_pix_y_s = max(y1 + y - int(pen_size/2), 0)
draw_pix_y_e = min(y1 + y + int(pen_size/2), self.org_img_height-1)
draw_pix_x_s = max(x - int(pen_size/2), 0)
draw_pix_x_e = min(x + int(pen_size/2), self.org_img_width-1)
# for Pen's size
for yy in range(draw_pix_y_s, draw_pix_y_e+1):
for xx in range(draw_pix_x_s, draw_pix_x_e+1):
self.layer_qimg.setPixelColor(xx, yy, pen_color)
Ajoutez `` Enregistrer l'image du calque '' au menu Fichier et lorsque vous le sélectionnez, il enregistre l'image dessinée de l'utilisateur. Plus précisément, le code sera le suivant, exécutez le processus de création d'une image QImage qui reflète le dessin avec make_layer_image () expliqué ci-dessus, ouvrez la boîte de dialogue de fichier pour l'enregistrement et enregistrez avec le nom de fichier image entré. Faire.
# Main Window components
class MainWindow(QMainWindow):
def __init__(self):
# Set "Save layer image" menu
self.layer_img_save_button = QAction(self.style().standardIcon(getattr(QStyle, 'SP_FileDialogEnd')), 'Save Layer Image', self)
self.layer_img_save_button.setShortcut('Ctrl+S')
self.layer_img_save_button.triggered.connect(self.save_layer_image)
self.file_menu.addAction(self.layer_img_save_button)
# Slot function of save layer image button clicked
def save_layer_image(self):
self.make_layer_image()
layer_img_default_path = self.app_setting["SoftwareSetting"]["file_path"]["layer_img_dir"]
options = QFileDialog.Options()
file_name, selected_filete = QFileDialog.getSaveFileName(self, 'Save layer image', layer_img_default_path, \
'image files(*.png, *jpg)', options=options)
self.layer_qimg.save(file_name)
ret = QMessageBox(self, 'Success', 'layer image is saved successfully', QMessageBox.Ok)
Qt est également livré avec un outil appelé Qt Designer qui organise des parties telles que des boutons sur l'écran de l'application que vous souhaitez créer sur l'écran GUI. Avant de vous y habituer, il est facile d'imaginer quel type de pièces (Widget) vous avez, donc il peut être plus facile de comprendre si vous essayez de faire une apparition en utilisant cela.
Le code source de l'application créée sera publié à l'emplacement suivant. Page de code source de l'application
Recommended Posts