You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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_()
问题分析与解决方案

核心问题

  1. numpy数组裁剪索引错误:PIL图像转numpy数组后,形状为(高度, 宽度, 通道数),即第一个维度是垂直方向(y轴),第二个是水平方向(x轴)。原代码中cropped_image= image_arr[x:x+w,y:y+h]的索引顺序颠倒,导致裁剪区域的宽高错位,非正方形时显示异常。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 07:37:08