如何在Python中用PIL图像数据填充QImage?Qt截图转换遇异常
问题描述
我希望在Python中使用Qt对截图进行裁剪:通过PIL抓取屏幕画面,将其转换为QImage后转为QPixmap显示在QLabel中。当前代码仅在宽高为正方形时正常运行,非正方形情况下,PIL图像转换为QImage的过程存在问题。
变量load_image_from_pil为False时从本地路径加载图片,为True时抓取屏幕画面,相关代码如下:
问题代码
import sys import cv2 import numpy as np from PIL import ImageGrab, Image from PySide2.QtCore import QSize, Qt, QRect from PySide2.QtGui import QImage,QColor,QPixmap from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton,QLabel #True = from screen using pil image grab #False = from path load_image_from_pil=True IMAGE_PATH=r"C:\Users\kosta\Pictures\Art_inspiration1.png" ##### EXPECTED FUNCTIONALITY ####### #crop a screenshot of the screen to x,y=0,0 with width=100 ,height=100 def NumpyArray_to_qimage(np_arr,x,y,w,h): #make qimage from numpy qimage = QImage(np_arr.data, w, h, 3 * w, QImage.Format_RGB888) return qimage def PIL_to_NumpyArray(pil_img): return np.array(pil_img) def PIL_to_qimage(pil_img, x,y,w,h): #convert pil to numpy array np_arr = PIL_to_NumpyArray(pil_img) qimage = NumpyArray_to_qimage(np_arr,x,y,w,h) return qimage # Subclass QMainWindow to customize your application's main window class MainWindow(QMainWindow): def __init__(self): super().__init__() # global x,y,w,h x,y,w,h=0,0,100,300 self.setWindowTitle("My App1") self.label = QLabel("label") self.label.setGeometry(QRect(x,y,w,h)) qimage=None # qimage = QImage(im_np.data, w, h, 3 * w, QImage.Format_RGB32) if load_image_from_pil: print("screenshot and cropping image") #screenshot whole screen grab_image = ImageGrab.grab() # crop portion of image image_arr = np.array(grab_image) cropped_image= image_arr[x:x+w,y:y+h] # cropped_image = Image.fromarray(np.uint8(cropped_image)).convert('RGB') # cropped_image = Image.fromarray(image_arr.astype('uint8'), 'RGB') cropped_image = Image.fromarray(cropped_image) cv2.imshow('Live', image_arr[y:y+h,x:x+w]) # image_arr = np.array(grab_image) # image_arr = image_arr[x:x+w,y:y+h] # image = Image.fromarray(image_arr) qimage = PIL_to_qimage(cropped_image,x,y,w,h) else: print("loading image from file") qimage = QImage(IMAGE_PATH) pixmap = QPixmap.fromImage(qimage) print(qimage) self.label.setPixmap(pixmap) # Set the central widget of the Window. self.setCentralWidget(self.label) self.resize(self.label.width(), self.label.height()) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec_()
问题分析与解决方案
核心问题
- numpy数组裁剪索引错误:PIL图像转numpy数组后,形状为
(高度, 宽度, 通道数),即第一个维度是垂直方向(y轴),第二个是水平方向(x轴)。原代码中cropped_image= image_arr[x:x+w,y:y+h]的索引顺序颠倒,导致裁剪区域的宽高错位,非正方形时显示异常。 - QImage创建时参数不匹配:原代码硬传初始的
w,h参数,但裁剪后的图像尺寸可能因索引错误出现偏差,应该直接使用裁剪后图像的实际宽高。
修正后的代码
import sys import numpy as np from PIL import ImageGrab, Image from PySide2.QtCore import QRect from PySide2.QtGui import QImage, QPixmap from PySide2.QtWidgets import QApplication, QMainWindow, QLabel # True = 抓取屏幕截图 | False = 加载本地图片 load_image_from_pil = True IMAGE_PATH = r"C:\Users\kosta\Pictures\Art_inspiration1.png" def numpy_to_qimage(np_arr): # 获取图像实际宽高和通道数 h, w, ch = np_arr.shape # 计算行字节跨度:RGB888格式每个像素3字节 stride = 3 * w # 创建QImage,注意numpy数组是RGB顺序,对应QImage.Format_RGB888 qimage = QImage(np_arr.data, w, h, stride, QImage.Format_RGB888) # 返回QImage的副本,避免原numpy数组被回收后QImage失效 return qimage.copy() def pil_to_qimage(pil_img): np_arr = np.array(pil_img) return numpy_to_qimage(np_arr) class MainWindow(QMainWindow): def __init__(self): super().__init__() # 裁剪区域参数:x,y为左上角坐标,w为宽度,h为高度 x, y, w, h = 0, 0, 100, 300 self.setWindowTitle("截图裁剪演示") self.label = QLabel() self.label.setGeometry(QRect(x, y, w, h)) qimage = None if load_image_from_pil: print("正在抓取屏幕并裁剪...") # 抓取整个屏幕 grab_image = ImageGrab.grab() # 转换为numpy数组,形状为(h, w, 3) image_arr = np.array(grab_image) # 修正裁剪索引:[y_start:y_end, x_start:x_end] cropped_arr = image_arr[y:y+h, x:x+w] # 转换为PIL图像(可选,也可以直接用numpy数组转QImage) cropped_image = Image.fromarray(cropped_arr) # 转换为QImage qimage = pil_to_qimage(cropped_image) else: print("正在加载本地图片...") qimage = QImage(IMAGE_PATH) # 裁剪本地图片 qimage = qimage.copy(x, y, w, h) pixmap = QPixmap.fromImage(qimage) self.label.setPixmap(pixmap) self.setCentralWidget(self.label) self.resize(w, h) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec_()
关键改动说明
- 修正裁剪索引:将
image_arr[x:x+w,y:y+h]改为image_arr[y:y+h, x:x+w],匹配numpy数组的维度顺序。 - 动态获取图像尺寸:在
numpy_to_qimage函数中直接从numpy数组提取宽高,避免硬编码参数导致的不匹配。 - 添加QImage副本:返回
qimage.copy(),防止原numpy数组被垃圾回收后QImage失去数据源。 - 简化本地图片裁剪:直接使用QImage的
copy方法完成裁剪,无需经过PIL/numpy转换。
内容的提问来源于stack exchange,提问作者kostas petsis
相关产品推荐
相关产品推荐

