QImage 会扭曲一些图像,但不会扭曲其他图像
QImage skews some images but not others
我正在使用 tif 堆栈,QImage
似乎将某些图像倾斜 45 度角。 Matplotlib 能够在两个测试用例中毫无问题地显示图像(links 到两个 tif 堆栈在下面提供)所以我不认为我在某处搞砸了我的阵列。
这是一个工作示例:(注意:为简单起见,此示例仅显示 tif 堆栈中的第一张图像)
import matplotlib.pyplot as plt
import sys
from PIL import Image
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtWidgets import (QMainWindow, QApplication, QVBoxLayout,
QWidget, QFileDialog, QGraphicsPixmapItem, QGraphicsView,
QGraphicsScene)
import numpy as np
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# set up a widget to hold a pixmap
wid = QWidget(self)
self.setCentralWidget(wid)
self.local_grview = QGraphicsView()
self.local_scene = QGraphicsScene()
vbox = QVBoxLayout()
self.local_grview.setScene( self.local_scene )
vbox.addWidget(self.local_grview)
wid.setLayout(vbox)
# load and display the image
self.loadImage()
# display the widget
self.show()
# also use matplotlib to display the data as it should appear
plt.imshow(self.dataUint8[0], cmap='gray')
plt.show()
def loadImage(self):
fname = QFileDialog.getOpenFileName(self, 'Open file', '/home')[0]
# use the tif reader to read in the tif stack
self.data = self.readTif(fname)
# convert to uint8 for display
self.dataUint8 = self.uint8Convert(self.data)
###############################################################################################################################
# I suspect this is where something goes wrong
###############################################################################################################################
# create a QImage object
self.im = QImage(self.dataUint8[0], self.dataUint8[0].shape[1], self.dataUint8[0].shape[0], QImage.Format_Grayscale8)
# if we save using self.im.save() we also have a skewed image
###############################################################################################################################
# send the QImage object to the pixmap generator
self.pixmap = QPixmap(self.im)
self.pixMapItem = QGraphicsPixmapItem(self.pixmap, None)
self.local_scene.addItem(self.pixMapItem)
def readTif(self, filename): # use this function to read in a tif stack and return a 3D numpy array
# read in the file
stack = Image.open(filename)
# extract each frame from the file and store in the frames variable
frames = []
i = 0
while True:
try:
stack.seek(i) # move to the ith position in the stack
frames.append(np.array(stack) )
i += 1
except EOFError:
# end of stack
break
del stack # probably unnecessary but this presumably saves a bit of memory
return frames
def uint8Convert(self, frames): # use this function to scale a 3D numpy array of floats to 0-255 so it plays well with Qt methods
# convert float array to uint8 array
if np.min(frames)<0:
frames_uint8 = [np.uint8((np.array(frames[i]) - np.min(frames[i]))/np.max(frames[i])*255) for i in range(np.shape(frames)[0])]
else:
frames_uint8 = [np.uint8(np.array(frames[i])/np.max(frames[i])*255) for i in range(np.shape(frames)[0])]
return frames_uint8
if __name__=='__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
这是输出的屏幕截图:
Qimage 与 matplotlib
这是一个 link 到正确显示的 tif 堆栈:
https://drive.google.com/uc?export=download&id=0B9EG5AHWC9qzX3NrNTJRb2toV2c
这是一个 link 显示时倾斜的 tif 堆栈:
https://drive.google.com/uc?export=download&id=0B9EG5AHWC9qzbFB4TDU4c2x1OE0
如果能帮助理解 QImage
歪曲这张图片的原因,我们将不胜感激。两个 tif 堆栈之间唯一的主要区别是,显示倾斜的堆栈在图像周围有一个填充的黑色区域(零),这使得阵列更大。
更新:我现在发现,如果我将有问题的图像裁剪为 1024x1024 或 512x512 或 1023x1024 QImage
显示正常,但裁剪 1024x1023 显示偏斜。因此,x(水平)长度似乎必须是 2 的幂,以便 QImage
能够按预期处理它。这是一个荒谬的限制!一定有什么我不明白的地方。它肯定有办法处理任意形状的数组。
...我想,原则上,可以先对图像应用倾斜,然后让 QImage
将其校正回来...(<== 不喜欢这个解决方案)
图像没有倾斜,基础数据被错误解释。
在您使用的构造函数中,数据缓冲区是平面的,您还必须以像素为单位指定行和列的大小。您以某种方式将行指定为太长,因此下一行的开头被换行到当前行的末尾。这就是为什么你得到图像的 "striping",以及为什么当你到达后面的行时会有越来越大的环绕。这也解释了为什么当您使用构造函数的 QImage(fname)
版本时它会起作用。该构造函数使用 Qt 库代码读取图像数据,这不存在您自己的代码存在的问题。
有几处数据可能读取不正确。我不知道 PIL 包的详细信息,但 np.array(stack)
行看起来像是一个似是而非的候选者。我不知道堆栈对象如何公开缓冲区接口,但它可能与您想象的不同,例如,数据是 column- 而不是 row-major。另请注意,您使用的 QImage
构造函数期望数据为 32 位对齐,即使对于 8 位数据也是如此。这可能是个问题。
另一个可能的候选方法是 uint8Convert
方法,它可能会无意中转置数据或滚动数据 forwards/backwards。这可能就是为什么方形尺寸有效,但矩形尺寸无效的原因。
非常感谢 bnaecker 提供 32 位对齐提示并向源提供 link。这是解决方案。
QImage
需要知道数组每行有多少字节,否则它只会猜测(在某些情况下它会猜错)。因此,在 loadImage()
函数中使用以下内容会产生正确的输出。
# get the shape of the array
nframes, height, width = np.shape(self.dataUint8)
# calculate the total number of bytes in the frame
totalBytes = self.dataUint8[0].nbytes
# divide by the number of rows
bytesPerLine = int(totalBytes/height)
# create a QImage object
self.im = QImage(self.dataUint8[0], width, height, bytesPerLine, QImage.Format_Grayscale8)
其余代码相同
我正在使用 tif 堆栈,QImage
似乎将某些图像倾斜 45 度角。 Matplotlib 能够在两个测试用例中毫无问题地显示图像(links 到两个 tif 堆栈在下面提供)所以我不认为我在某处搞砸了我的阵列。
这是一个工作示例:(注意:为简单起见,此示例仅显示 tif 堆栈中的第一张图像)
import matplotlib.pyplot as plt
import sys
from PIL import Image
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtWidgets import (QMainWindow, QApplication, QVBoxLayout,
QWidget, QFileDialog, QGraphicsPixmapItem, QGraphicsView,
QGraphicsScene)
import numpy as np
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# set up a widget to hold a pixmap
wid = QWidget(self)
self.setCentralWidget(wid)
self.local_grview = QGraphicsView()
self.local_scene = QGraphicsScene()
vbox = QVBoxLayout()
self.local_grview.setScene( self.local_scene )
vbox.addWidget(self.local_grview)
wid.setLayout(vbox)
# load and display the image
self.loadImage()
# display the widget
self.show()
# also use matplotlib to display the data as it should appear
plt.imshow(self.dataUint8[0], cmap='gray')
plt.show()
def loadImage(self):
fname = QFileDialog.getOpenFileName(self, 'Open file', '/home')[0]
# use the tif reader to read in the tif stack
self.data = self.readTif(fname)
# convert to uint8 for display
self.dataUint8 = self.uint8Convert(self.data)
###############################################################################################################################
# I suspect this is where something goes wrong
###############################################################################################################################
# create a QImage object
self.im = QImage(self.dataUint8[0], self.dataUint8[0].shape[1], self.dataUint8[0].shape[0], QImage.Format_Grayscale8)
# if we save using self.im.save() we also have a skewed image
###############################################################################################################################
# send the QImage object to the pixmap generator
self.pixmap = QPixmap(self.im)
self.pixMapItem = QGraphicsPixmapItem(self.pixmap, None)
self.local_scene.addItem(self.pixMapItem)
def readTif(self, filename): # use this function to read in a tif stack and return a 3D numpy array
# read in the file
stack = Image.open(filename)
# extract each frame from the file and store in the frames variable
frames = []
i = 0
while True:
try:
stack.seek(i) # move to the ith position in the stack
frames.append(np.array(stack) )
i += 1
except EOFError:
# end of stack
break
del stack # probably unnecessary but this presumably saves a bit of memory
return frames
def uint8Convert(self, frames): # use this function to scale a 3D numpy array of floats to 0-255 so it plays well with Qt methods
# convert float array to uint8 array
if np.min(frames)<0:
frames_uint8 = [np.uint8((np.array(frames[i]) - np.min(frames[i]))/np.max(frames[i])*255) for i in range(np.shape(frames)[0])]
else:
frames_uint8 = [np.uint8(np.array(frames[i])/np.max(frames[i])*255) for i in range(np.shape(frames)[0])]
return frames_uint8
if __name__=='__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
这是输出的屏幕截图:
Qimage 与 matplotlib
这是一个 link 到正确显示的 tif 堆栈:
https://drive.google.com/uc?export=download&id=0B9EG5AHWC9qzX3NrNTJRb2toV2c
这是一个 link 显示时倾斜的 tif 堆栈:
https://drive.google.com/uc?export=download&id=0B9EG5AHWC9qzbFB4TDU4c2x1OE0
如果能帮助理解 QImage
歪曲这张图片的原因,我们将不胜感激。两个 tif 堆栈之间唯一的主要区别是,显示倾斜的堆栈在图像周围有一个填充的黑色区域(零),这使得阵列更大。
更新:我现在发现,如果我将有问题的图像裁剪为 1024x1024 或 512x512 或 1023x1024 QImage
显示正常,但裁剪 1024x1023 显示偏斜。因此,x(水平)长度似乎必须是 2 的幂,以便 QImage
能够按预期处理它。这是一个荒谬的限制!一定有什么我不明白的地方。它肯定有办法处理任意形状的数组。
...我想,原则上,可以先对图像应用倾斜,然后让 QImage
将其校正回来...(<== 不喜欢这个解决方案)
图像没有倾斜,基础数据被错误解释。
在您使用的构造函数中,数据缓冲区是平面的,您还必须以像素为单位指定行和列的大小。您以某种方式将行指定为太长,因此下一行的开头被换行到当前行的末尾。这就是为什么你得到图像的 "striping",以及为什么当你到达后面的行时会有越来越大的环绕。这也解释了为什么当您使用构造函数的 QImage(fname)
版本时它会起作用。该构造函数使用 Qt 库代码读取图像数据,这不存在您自己的代码存在的问题。
有几处数据可能读取不正确。我不知道 PIL 包的详细信息,但 np.array(stack)
行看起来像是一个似是而非的候选者。我不知道堆栈对象如何公开缓冲区接口,但它可能与您想象的不同,例如,数据是 column- 而不是 row-major。另请注意,您使用的 QImage
构造函数期望数据为 32 位对齐,即使对于 8 位数据也是如此。这可能是个问题。
另一个可能的候选方法是 uint8Convert
方法,它可能会无意中转置数据或滚动数据 forwards/backwards。这可能就是为什么方形尺寸有效,但矩形尺寸无效的原因。
非常感谢 bnaecker 提供 32 位对齐提示并向源提供 link。这是解决方案。
QImage
需要知道数组每行有多少字节,否则它只会猜测(在某些情况下它会猜错)。因此,在 loadImage()
函数中使用以下内容会产生正确的输出。
# get the shape of the array
nframes, height, width = np.shape(self.dataUint8)
# calculate the total number of bytes in the frame
totalBytes = self.dataUint8[0].nbytes
# divide by the number of rows
bytesPerLine = int(totalBytes/height)
# create a QImage object
self.im = QImage(self.dataUint8[0], width, height, bytesPerLine, QImage.Format_Grayscale8)
其余代码相同