優化使用PyQt5和圖像的python程序的速度

我正在嘗試創建一個python程序,讓我想象一個風格化的虛構政治地圖,其中每個國家都用特定顏色的像素表示。我正在使用PyQt5創建一些簡單的GUI,讓我打開地圖,縮放和拖動,當你點擊地圖時,它會在窗口的一側顯示一些信息,比如國家名稱和首都。其中一個功能是,一旦單擊某個國家,它的顏色就會變為亮綠色,這樣就可以清楚地選擇哪個國家。我實現這一點的方式對小圖像很好,但對大圖像來說很難,從我單擊一個國家到國家顏色變為綠色的那一刻,時間長達6-7秒。

這是我用來在點擊國家時實現顏色改變的代碼:

    def highlight_pixels(self, RGB):
        #BLOCK ONE
        #Timer 1 starts
        start_time = time.perf_counter()
        # Copy a numpy array that represent the base image. This is so that every time the highlight_pixels function is called it reverts previous changes
        temp_image = self.base_image_array.copy()
        # Print time 1
        end_time = time.perf_counter()
        elapsed_time = (end_time - start_time) * 1000
        print(f"Time taken to initialise image: {elapsed_time:.6f} milliseconds")
        
        #BLOCK TWO
        #Timer 2 starts
        start_time = time.perf_counter()
        # Select pixels that match the target color
        mask = (temp_image[:,:,0] == RGB[0]) & (temp_image[:,:,1] == RGB[1]) & (temp_image[:,:,2] == RGB[2])
        # Set color of the selected pixels to a bright green.
        temp_image[self.ignore_color_mask & mask, :] = (83, 255, 26)
        # Print time 2
        end_time = time.perf_counter()
        elapsed_time = (end_time - start_time) * 1000
        print(f"Time taken to change color: {elapsed_time:.6f} milliseconds")
        
        #BLOCK THREE
        #Timer 3 starts
        start_time = time.perf_counter()
        # Convert array back to image (qimage)
        temp_image = qimage2ndarray.array2qimage(temp_image)
        # convert the image back to pixmap
        self.map_image = QPixmap.fromImage(temp_image)
        # update the map scene
        self.view.scene().clear()
        self.view.scene().addPixmap(self.map_image)
        # Print time 3
        end_time = time.perf_counter()
        elapsed_time = (end_time - start_time) * 1000
        print(f"Time taken to show image: {elapsed_time:.6f} milliseconds")

在一次包含兩個圖像的測試中,一個大圖像,一個小圖像,我得到了這三個塊的時間。很明顯,每次我運行程序時都會有一點變化,但結果總是非常相似:

我嘗試了一些不同的方法。我首先很難嘗試用綠色像素創建一個新圖像,并將其覆蓋在基礎圖像上,而不是對整個圖像進行更改,但我認為這沒有正確實現,因此決定返回。

然后我想我可以通過創建一個包含特定顏色所有像素坐標的字典來加快速度。這在某種程度上起了作用,這意味著在小圖像(200x100像素)上測試時,它顯示出一些改進的跡象:

然而,當嘗試對大圖像(16300x8150像素)使用這種方法時,在嘗試創建字典時,它會耗盡內存。

這是更新的功能:

    def highlight_pixels(self, RGB):
        #BLOCK ONE
        #Timer 1 starts
        start_time = time.perf_counter()
        # Copy a numpy array that represent the base image. This is so that every time the highlight_pixels function is called it reverts previous changes
        temp_image = self.base_image_array.copy()
        # Print time 1
        end_time = time.perf_counter()
        elapsed_time = (end_time - start_time) * 1000
        print(f"Time taken to initialise image: {elapsed_time:.6f} milliseconds")
        
        #BLOCK TWO
        #Timer 2 starts
        start_time = time.perf_counter()
        # Select pixels that match the target color
        coordinates = self.color_dict.get(RGB)
        # Set their color to green
        temp_image[coordinates[:, 0], coordinates[:, 1], :] = (83, 255, 26)    
        # Print time 2
        end_time = time.perf_counter()
        elapsed_time = (end_time - start_time) * 1000
        print(f"Time taken to change color: {elapsed_time:.6f} milliseconds")
        
        #BLOCK THREE
        #Timer 3 starts
        start_time = time.perf_counter()
        #convert array back to image (qimage)
        temp_image = qimage2ndarray.array2qimage(temp_image)
        # convert the image back to pixmap
        self.map_image = QPixmap.fromImage(temp_image)
        # update the map scene
        self.view.scene().clear()
        self.view.scene().addPixmap(self.map_image)
        # Print time 3
        end_time = time.perf_counter()
        elapsed_time = (end_time - start_time) * 1000
        print(f"Time taken to show image: {elapsed_time:.6f} milliseconds")

下面是創建我提到的字典的函數:

def create_color_dict(self, image_array):
        self.color_dict = {}
        # Create an array of indices for each pixel
        pixels = np.indices(image_array.shape[:2]).T.reshape(-1, 2)
        # Get the color of each pixel
        pixel_colors = image_array[pixels[:, 0], pixels[:, 1], :]
        # Convert pixel colors to tuples
        pixel_colors = tuple(map(tuple, pixel_colors))
        # Create dictionary of pixels by color
        for color, pixel in zip(pixel_colors, pixels):
            if color not in self.color_dict:
                self.color_dict[color] = np.array([pixel])
            else:
                self.color_dict[color] = np.append(self.color_dict[color], [pixel], axis=0)
        return self.color_dict

我還嘗試制作一個單獨的程序來創建字典(因為我只需要運行一次該部分,真的),但它顯然也耗盡了內存。

所以我的問題是,如何優化這一點?我嘗試的方法有效嗎?如果是這樣的話,我如何避免內存不足?

我非常樂意提供任何其他信息,包括整個代碼或測試圖像。我是一個絕對的初學者,很難理解如何不讓某個東西正常工作,而是如何讓它更好地工作。

編輯:這是所有代碼,以防有人問,因為我明天才能訪問電腦。請注意,這可能有點令人困惑,因為我對此一無所知。

import sys
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtGui import QImage, QPixmap, QColor, QPainter, QPen, QBrush, qRed, qGreen, qBlue, QFont
from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QLabel, QVBoxLayout, QWidget, QGraphicsRectItem, QMainWindow, QDockWidget, QHBoxLayout, QSizePolicy, QGraphicsPixmapItem
import time
import qimage2ndarray
from qimage2ndarray import rgb_view
import numpy as np

class MapViewer(QMainWindow):
    def __init__(self, map_path):
        super().__init__()
        self.setWindowTitle("Map Viewer")
        self.setGeometry(100, 100, 800, 600)
        self.dragCheck = False #False if not dragging, True if dragging

        # Create the QGraphicsView widget and set it as the central widget
        self.view = QGraphicsView(self)
        self.setCentralWidget(self.view)
        self.view.setRenderHint(QPainter.Antialiasing)
        self.view.setRenderHint(QPainter.SmoothPixmapTransform)
        self.view.setDragMode(QGraphicsView.NoDrag)
        self.view.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.view.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.view.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
        self.view.setOptimizationFlag(QGraphicsView.DontAdjustForAntialiasing, True)
        self.view.setOptimizationFlag(QGraphicsView.DontSavePainterState, True)

        # Load the map image and set it as the background image
        self.map_image = QPixmap(map_path)
        #self.original_map_image = self.map_image.copy() # Assign the original image to this variable
        #self.map_item = QGraphicsPixmapItem(self.map_image)
        self.view.setScene(QGraphicsScene())
        self.view.scene().addPixmap(self.map_image)
        #self.view.fitInView(self.view.sceneRect(), Qt.KeepAspectRatio)
        
        # convert the pixmap to image
        self.base_image = QImage(map_path)
        # Convert image into numpy array
        self.base_image_array = rgb_view(self.base_image)
        
        # Create a mask with the same shape as the image, filled with True
        self.ignore_color_mask = np.ones(self.base_image_array.shape[:2], dtype=bool)
        # Set False to pixels that have black color (0, 0, 0) or sea color (172, 208, 239)
        self.ignore_color_mask[np.where((self.base_image_array[:,:,0]==0) & (self.base_image_array[:,:,1]==0) & (self.base_image_array[:,:,2]==0)| (self.base_image_array[:,:,0] == 172) & (self.base_image_array[:,:,1] == 208) & (self.base_image_array[:,:,2] == 239))] = False
        
        # Make it so the wheel zooms in and out
        self.view.wheelEvent = self.handle_mouse_wheel
        
        # Install an eventFilter to handle the mouse clicks under certain conditions only (to allow panning when pressing control)
        self.view.installEventFilter(self)
        
        # Handle ctrl beign pressed or released
        self.view.keyPressEvent = self.CtrlPressEvent
        self.view.keyReleaseEvent = self.CtrlReleaseEvent
        
        # Display the default country information in the dock widget
        RGB = (255, 0, 0)
        self.countries = {
            (255, 0, 0): {"name": "Red", "rgb": (255, 0, 0), "capital": "Red Capital", "government": "Red Government", "size": "Red Size", "population": "Red Population"},
            (0, 255, 0): {"name": "Green", "rgb": (0, 255, 0), "capital": "Green Capital", "government": "Green Government", "size": "Green Size", "population": "Green Population"},
            (0, 0, 255): {"name": "Blue", "rgb": (0, 0, 255), "capital": "Blue Capital", "government": "Blue Government", "size": "Blue Size", "population": "Blue Population"}
        }
        self.display_country_info(RGB)
        self.create_color_dict(self.base_image_array)

    def create_color_dict(self, image_array):
        self.color_dict = {}
        # Create an array of indices for each pixel
        pixels = np.indices(image_array.shape[:2]).T.reshape(-1, 2)
        # Get the color of each pixel
        pixel_colors = image_array[pixels[:, 0], pixels[:, 1], :]
        # Convert pixel colors to tuples
        pixel_colors = tuple(map(tuple, pixel_colors))
        # Create dictionary of pixels by color
        for color, pixel in zip(pixel_colors, pixels):
            if color not in self.color_dict:
                self.color_dict[color] = np.array([pixel])
            else:
                self.color_dict[color] = np.append(self.color_dict[color], [pixel], axis=0)
        return self.color_dict

        
        
    def handle_mouse_wheel(self, event):
        if event.angleDelta().y() > 0:
            # Scroll up, zoom in
            self.view.scale(1.1, 1.1)
        else:
            # Scroll down, zoom out
            self.view.scale(1 / 1.1, 1 / 1.1)

    def display_country_info(self, RGB):
        # Look up the country information based on the RGB value
        country_info = self.countries.get(RGB)
        if country_info is None:
            # Handle the case where the RGB value is not found in the dictionary
            print("Sorry, no country found with that RGB value.")
            return
        else:
            #Remove any existing dock widgets
            for dock_widget in self.findChildren(QDockWidget):
                self.removeDockWidget(dock_widget)

            # Create a QVBoxLayout to hold the labels
            layout = QVBoxLayout()
            
            # Create a QLabel for each piece of information and add it to the layout
            name_label = QLabel("Name:")
            name_label.setStyleSheet("font-weight: bold;")
            name_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
            value_label = QLabel(f"{country_info['name']}")
            value_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

            # create a new layout for the name and value labels
            layout_name_value = QHBoxLayout()

            # add both labels to the new layout
            layout_name_value.addWidget(name_label)
            layout_name_value.addWidget(value_label)

            # add this new layout to the main layout
            layout.addLayout(layout_name_value)
         
            # Create a QLabel for each piece of information and add it to the layout
            name_label = QLabel("RGB:")
            name_label.setStyleSheet("font-weight: bold;")
            name_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
            value_label = QLabel(f"{country_info['rgb']}")
            value_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

            # create a new layout for the name and value labels
            layout_name_value = QHBoxLayout()

            # add both labels to the new layout
            layout_name_value.addWidget(name_label)
            layout_name_value.addWidget(value_label)

            # add this new layout to the main layout
            layout.addLayout(layout_name_value)
            
            # Create a QLabel for each piece of information and add it to the layout
            name_label = QLabel("Capital:")
            name_label.setStyleSheet("font-weight: bold;")
            name_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
            value_label = QLabel(f"{country_info['capital']}")
            value_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

            # create a new layout for the name and value labels
            layout_name_value = QHBoxLayout()

            # add both labels to the new layout
            layout_name_value.addWidget(name_label)
            layout_name_value.addWidget(value_label)

            # add this new layout to the main layout
            layout.addLayout(layout_name_value)
            
            # Create a QLabel for each piece of information and add it to the layout
            name_label = QLabel("Government:")
            name_label.setStyleSheet("font-weight: bold;")
            name_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
            value_label = QLabel(f"{country_info['government']}")
            value_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

            # create a new layout for the name and value labels
            layout_name_value = QHBoxLayout()

            # add both labels to the new layout
            layout_name_value.addWidget(name_label)
            layout_name_value.addWidget(value_label)

            # add this new layout to the main layout
            layout.addLayout(layout_name_value)
            
            # Create a QLabel for each piece of information and add it to the layout
            name_label = QLabel("Size:")
            name_label.setStyleSheet("font-weight: bold;")
            name_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
            value_label = QLabel(f"{country_info['size']}")
            value_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

            # create a new layout for the name and value labels
            layout_name_value = QHBoxLayout()

            # add both labels to the new layout
            layout_name_value.addWidget(name_label)
            layout_name_value.addWidget(value_label)

            # add this new layout to the main layout
            layout.addLayout(layout_name_value) 

            # Create a QLabel for each piece of information and add it to the layout
            name_label = QLabel("Population:")
            name_label.setStyleSheet("font-weight: bold;")
            name_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
            value_label = QLabel(f"{country_info['population']}")
            value_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

            # create a new layout for the name and value labels
            layout_name_value = QHBoxLayout()

            # add both labels to the new layout
            layout_name_value.addWidget(name_label)
            layout_name_value.addWidget(value_label)

            # add this new layout to the main layout
            layout.addLayout(layout_name_value) 

            # Create a QWidget to hold the layout and add it to the right dock
            
            widget = QWidget()
            widget.setLayout(layout)
            dock_widget = QDockWidget()
            dock_widget.setWindowTitle("Country Informations")
            dock_widget.setWidget(widget)
            self.addDockWidget(Qt.RightDockWidgetArea, dock_widget)
            dock_widget.setFeatures(QDockWidget.NoDockWidgetFeatures) # removes close button and full screen button
            #dock_widget.setFixedSize(int(self.width()*0.20), self.height())
            dock_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    def highlight_pixels(self, RGB):
        #BLOCK ONE
        #Timer 1 starts
        start_time = time.perf_counter()
        # Copy a numpy array that represent the base image. This is so that every time the highlight_pixels function is called it reverts previous changes
        temp_image = self.base_image_array.copy()
        # Print time 1
        end_time = time.perf_counter()
        elapsed_time = (end_time - start_time) * 1000
        print(f"Time taken to initialise image: {elapsed_time:.6f} milliseconds")
        
        #BLOCK TWO
        #Timer 2 starts
        start_time = time.perf_counter()
        # Select pixels that match the target color
        coordinates = self.color_dict.get(RGB)
        # Set their color to green
        temp_image[coordinates[:, 0], coordinates[:, 1], :] = (83, 255, 26)    
        # Print time 2
        end_time = time.perf_counter()
        elapsed_time = (end_time - start_time) * 1000
        print(f"Time taken to change color: {elapsed_time:.6f} milliseconds")
        
        #BLOCK THREE
        #Timer 3 starts
        start_time = time.perf_counter()
        #convert array back to image (qimage)
        temp_image = qimage2ndarray.array2qimage(temp_image)
        # convert the image back to pixmap
        self.map_image = QPixmap.fromImage(temp_image)
        # update the map scene
        self.view.scene().clear()
        self.view.scene().addPixmap(self.map_image)
        # Print time 3
        end_time = time.perf_counter()
        elapsed_time = (end_time - start_time) * 1000
        print(f"Time taken to show image: {elapsed_time:.6f} milliseconds")
        
    # Set up an event filter to recognize whether it should pan around or print informations.
    def eventFilter(self, obj, event):
        if event.type() == QEvent.MouseButtonPress and event.button() == Qt.LeftButton:
            if self.dragCheck == False:
                # Get the position of the mouse click in viewport coordinates
                pos = event.pos()
                # Convert the viewport coordinates to scene coordinates
                scene_pos = self.view.mapToScene(pos)
                # Get the pixel at the scene coordinates
                pixel = self.map_image.toImage().pixel(int(scene_pos.x()), int(scene_pos.y()))
                # Get the red, green, and blue components of the pixel
                red = qRed(pixel)
                green = qGreen(pixel)
                blue = qBlue(pixel)
                # Assign the RGB values to the RGB variable
                RGB = (red, green, blue)
                print("RGB:", RGB) # You can replace this with the call to display_country_info with the RGB variable
                self.display_country_info(RGB)
                self.highlight_pixels(RGB)
                return True
        return QMainWindow.eventFilter(self, obj, event)
    
    # Check if Ctrl is beign pressed
    def CtrlPressEvent(self, event):
        if event.key() == Qt.Key_Control:
            self.dragCheck = True
            self.view.setDragMode(QGraphicsView.ScrollHandDrag)
            #print("drag")
            
            
    # Check if Ctrl is beign released        
    def CtrlReleaseEvent(self, event):
        if event.key() == Qt.Key_Control:
            self.dragCheck = False
            self.view.setDragMode(QGraphicsView.NoDrag)
            #print("nodrag", self.dragCheck)
            

          
if __name__ == "__main__":
    app = QApplication(sys.argv)
    viewer = MapViewer("map.png")
    #viewer = MapViewer("countries_ee.png")
    viewer.show()
    
    sys.exit(app.exec_())
? 最佳回答:

由于musicamante的提示,當用戶點擊“國家”顏色時,我能夠獲得更好的方法來更改它們。我實現這一點的方法是首先分解基礎圖像(“政治地圖”),以便將每個“國家”保存為具有透明背景的單個png圖像。然后,我通過為每個“國家”創建一個QGraphicsPixmapItem并將它們添加到正確坐標的視圖中,重新創建了“地圖”,有點像拼圖。此時,由于每個國家都是一個單獨的QGraphicsPixmapItem,因此使用QGraphicsColorizeEffect()可以更容易地更改它們的顏色,該方法會對應用到的項目應用顏色。

這真的很快,因為它不需要像我最初嘗試的那樣,從數組到QImage再到整個圖像的QPixmap的各種轉換(據我理解)。

主站蜘蛛池模板: 久久精品国产第一区二区| 精品国产日韩亚洲一区| 合区精品久久久中文字幕一区| 欧洲精品无码一区二区三区在线播放| 亚洲熟妇AV一区二区三区浪潮 | 国产福利一区二区三区在线观看| 日韩精品电影一区亚洲| 午夜性色一区二区三区不卡视频| 亚洲国产一区二区a毛片| 国精品无码一区二区三区在线蜜臀| 视频精品一区二区三区| 国产av夜夜欢一区二区三区| 精品一区二区三区| 韩国一区二区三区视频| 日韩伦理一区二区| 国产一区二区三区不卡在线观看| 国产精品久久久久一区二区三区| 国产精品视频一区麻豆| 亚洲av乱码一区二区三区 | 国产一区二区精品久久岳√ | 精品无码一区在线观看| 国产激情视频一区二区三区| 久久精品一区二区东京热| 国产AV午夜精品一区二区三区| 国产区精品一区二区不卡中文| 亚洲韩国精品无码一区二区三区 | 国产乱码精品一区二区三区四川人 | 无码人妻精品一区二区三区蜜桃| 在线观看亚洲一区二区| 无码夜色一区二区三区| 手机福利视频一区二区| 日亚毛片免费乱码不卡一区| 中文字幕一区二区日产乱码| 亚洲色欲一区二区三区在线观看| 寂寞一区在线观看| 色婷婷亚洲一区二区三区| 精品在线一区二区三区| 亚洲线精品一区二区三区| 日韩在线一区二区| 国产一区韩国女主播| 一区二区三区国产|