将16位灰度转换为QImage

4
我正在开发一个基于PyQt4 GUI的传感器Python应用程序。该传感器生成16位测量值...每个“行”有256个16位“像素”。通过获取256行来获得一个正方形的“图像”,从而产生一个(256,256)的16位数字Numpy数组。我只想将其显示为灰度图像。传感器循环在QThread中运行并发出QImage信号。该信号连接到一个插槽,通过将数据打包成32位RGB图像在主GUI中呈现数据。当然,为了将16位灰度像素打包成32位RGB图像,我被迫将16位像素缩放为8位,并且会失去大量的动态范围。提供了一个MWE,展示了我的当前策略(这显然不是我的更大的线程传感器应用程序...它仅提取了显著部分)。请注意,我是一个Python初学者,正在尽我最大努力跟上...
#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
Grayscale to RGB32 QPixmap tests
"""

import sys
import numpy as np
from PyQt4 import QtGui, QtCore

class PixmapTest(QtGui.QWidget):

    def __init__(self):
        super(PixmapTest, self).__init__()
        self.initUI()

    def initUI(self):      
        imglayout = QtGui.QHBoxLayout(self)

        img_16bit = np.random.randint(0,65535,size=(256,256)).astype(np.uint32)
        img_16bit_to_8bit = (img_16bit / 65535.0 * 255).astype(np.uint32)
        packed_img_array = (255 << 24 | (img_16bit_to_8bit) << 16 | (img_16bit_to_8bit) << 8 | (img_16bit_to_8bit)).flatten()
        img = QtGui.QImage(packed_img_array, 256, 256, QtGui.QImage.Format_RGB32)
        pixmap = QtGui.QPixmap(img.scaledToWidth(img.width()*2))

        imglabel = QtGui.QLabel(self)
        imglabel.setPixmap(pixmap)

        imglayout.addWidget(imglabel)
        self.setLayout(imglayout)

        self.move(300, 200)
        self.setWindowTitle('QPixmap Test')
        self.show()        

def main():

    app = QtGui.QApplication(sys.argv)
    form = PixmapTest()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main() 

具体来说,我的问题如下:

  1. 有没有更好的办法?解决方案必须保持“轻量级”(即 PyQt4 QImage/QPixmap)。我不能使用 Matplotlib 或其他重型库,因为这样太慢了。越接近本机 Python/Numpy 越好。我意识到这最终是 QImage 类的限制,但我希望有一个聪明的解决方案,让我保留当前的信号/槽“连接”。

  2. 通过试验,我发现我必须将最终被处理成 QImage 的所有数组都声明为 np.uint32(尽管 np.int32 似乎也可以工作)。如果我只是将倒数第二个数组声明为 uint32/int32,它就行不通。我不明白为什么。

  3. 我已经尝试过使用 Y' = 0.2126 * R + 0.7152 * G + 0.0722 * B 和其他类似的转换来改变亮度。可能在“抛光一坨屎”,但我想包括这个问题,因为 SX 上的其他答案似乎表明这很重要。虽然存在动态范围的损失,但看起来简单地将相同的值赋给 R、G、B 就可以了。

根据下面的评论要求,这里是传感器的一些样本数据的直方图以说明动态范围:

实际传感器数据的直方图


你能展示传感器数据的直方图吗?也许它没有使用整个16位的范围。例如,你可以通过 img_16bit / (img_16bit.max()/255.0) 将最大值变为白色。你还可以找到传感器数据的最大值和最小值,并将它们映射到 255 和 0。 - HYRY
@HYRY,我添加了一个直方图,显示我正在使用16位的全部范围... - ph0t0n
因此,大多数数据都在25000-45000范围内,将数据剪裁到此范围并映射到0-255可能会获得更好的结果。 - HYRY
从另一个角度来看,我在想编码16位灰度是否真的重要。我在qtcentre上找到了一篇帖子,似乎表明典型的显示器无法显示超过8位灰度。其他人则认为,典型的人类只能感知32到64个灰度级别。 - ph0t0n
@HYRY 裁剪数据不是一个选项...那些离群的数据点和范围在25000-45000之间的数据一样重要(如果不是更重要),因为它们很可能是由信号而非噪声引起的。对于传感器数据,每个像素都很重要... - ph0t0n
此外,就我所知,该直方图有256个箱子。从某种程度上讲,它阐明了我所关注的问题:在信号周围16个计数的大脉冲被分组为单个的8位值...但是实际数据显示,它们涵盖了大约2400-2600的范围,并且分布大致相等。 - ph0t0n
1个回答

3

以下是我用于演示的一些函数数据:

y, x = np.mgrid[-10:10:256j, -10:10:256j]
data = ((np.sin(y**2 + x**2) + 2) * 1000).astype(np.uint16)

img_8bit = (data / 256.0).astype(np.uint8) # use the high 8bit
img_8bit = ((data - data.min()) / (data.ptp() / 255.0)).astype(np.uint8) # map the data range to 0 - 255
img = QtGui.QImage(img_8bit.repeat(4), 256, 256, QtGui.QImage.Format_RGB32)

当使用高8位时,它看起来像这样: enter image description here 当将最小值和最大值映射到(0,255)时,它看起来像这样: enter image description here 要将8位图像转换为32位,您可以调用img_8bit.repeat(4),这将重复每个字节4次,因此内存可以被视为uint32缓冲区。由于您通过Format_RGB32而不是Format_ARGB32创建了QImage,所以最高有效字节未使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接