如何将彩色地图图像反转为标量值

时间:2010-09-15 18:57:47

标签: python image matplotlib scipy

如何反转颜色映射图像?

我有一个2D图像,可以在色图上绘制数据。我想读取图像并“反转”颜色贴图,即查找特定的RGB值,然后将其转换为浮点数。

例如: 使用此图片:http://matplotlib.sourceforge.net/_images/mri_demo.png

我应该能够得到一个440x360的浮点矩阵,知道色图是cm.jet

from pylab import imread
import matplotlib.cm as cm
a=imread('mri_demo.png')
b=colormap2float(a,cm.jet) #<-tricky part

4 个答案:

答案 0 :(得分:8)

可能有更好的方法来做到这一点;我不确定。 如果您阅读help(cm.jet),您将看到用于将区间[0,1]中的值映射到RGB 3元组的算法。你可以用一点纸和铅笔计算公式来反转定义映射的分段线性函数。

然而,有许多问题使得纸和铅笔解决方案有些缺乏吸引力:

  1. 这是一个很费力的代数,而且 该解决方案特定于cm.jet。 你必须再做所有这些工作 如果你改变了颜色图。如何自动解决这些代数方程很有意思,但这不是我知道如何解决的问题。

  2. 通常,颜色图可能不是 可逆的(可能有多个值) 被映射到相同的颜色)。在里面 cm.jet的情况,值在0.11之间 和0.125都映射到RGB 例如,3元组(0,0,1)。因此,如果 你的图像包含纯蓝色 像素,真的没办法 告诉它是否来自0.11的值 或者值,比方说,0.125。

  3. 从[0,1]到。的映射 3元组是3空间的曲线。该 你的形象中的颜色可能不会说谎 完美地在这条曲线上。有可能 例如,圆整错误。因此,任何实际的解决方案都必须能够插入或以某种方式将3空间中的点投影到曲线上。
  4. 由于非唯一性问题以及投影/插值问题,您可能会遇到许多可能的解决方案。以下只是一种可能性。

    以下是解决唯一性和投影/插值问题的一种方法:

    创建一个充当“代码簿”的gradientgradient是cm.jet颜色映射中的RGBA 4元组数组。 gradient的颜色对应于0到1之间的值。使用scipy的矢量量化函数scipy.cluster.vq.vq将图像中的所有颜色mri_demo.png映射到gradient中最近的颜色。 由于颜色贴图可以对许多值使用相同的颜色,因此渐变可以包含重复的颜色。我将它留给scipy.cluster.vq.vq来决定哪个(可能)非唯一的代码簿索引与特定颜色相关联。

    import matplotlib.pyplot as plt
    import matplotlib.cm as cm
    import numpy as np
    import scipy.cluster.vq as scv
    
    def colormap2arr(arr,cmap):    
        # http://stackoverflow.com/questions/3720840/how-to-reverse-color-map-image-to-scalar-values/3722674#3722674
        gradient=cmap(np.linspace(0.0,1.0,100))
    
        # Reshape arr to something like (240*240, 4), all the 4-tuples in a long list...
        arr2=arr.reshape((arr.shape[0]*arr.shape[1],arr.shape[2]))
    
        # Use vector quantization to shift the values in arr2 to the nearest point in
        # the code book (gradient).
        code,dist=scv.vq(arr2,gradient)
    
        # code is an array of length arr2 (240*240), holding the code book index for
        # each observation. (arr2 are the "observations".)
        # Scale the values so they are from 0 to 1.
        values=code.astype('float')/gradient.shape[0]
    
        # Reshape values back to (240,240)
        values=values.reshape(arr.shape[0],arr.shape[1])
        values=values[::-1]
        return values
    
    arr=plt.imread('mri_demo.png')
    values=colormap2arr(arr,cm.jet)    
    # Proof that it works:
    plt.imshow(values,interpolation='bilinear', cmap=cm.jet,
               origin='lower', extent=[-3,3,-3,3])
    plt.show()
    

    您看到的图像应该接近于再现mri_demo.png:

    alt text

    (原始mri_demo.png有一个白色边框。由于白色不是cm.jet中的颜色,请注意scipy.cluster.vq.vq将白色映射到gradient代码簿中的最近点,是一种淡绿色。)

答案 1 :(得分:1)

这是一种更简单的方法,适用于许多颜色图,例如viridis,但不适用于诸如“喷射”之类的LinearSegmentedColormap

颜色图存储为[r,g,b]值的列表。对于许多色图,此图恰好具有256个条目。使用颜色列表中最近的邻居查找0到1之间的值。因此,您无法获得确切的值,只能得到一个近似值。

一些用于说明概念的代码:

from matplotlib import pyplot as plt

def find_value_in_colormap(tup, cmap):
    # for a cmap like viridis, the result of the colormap lookup is a tuple (r, g, b, a), with a always being 1
    # but the colors array is stored as a list [r, g, b]
    # for some colormaps, the situation is reversed: the lookup returns a list, while the colors array contains tuples
    tup = list(tup)[:3]
    colors = cmap.colors
    if tup in colors:
        ind = colors.index(tup)
    elif tuple(tup) in colors:
        ind = colors.index(tuple(tup))
    else: # tup was not generated by this colormap
        return None
    return (ind + 0.5) / len(colors)

val = 0.3
tup = plt.cm.viridis(val)

print(find_value_in_colormap(tup, plt.cm.viridis))

这会打印出近似值:

0.298828125

是对应于颜色三倍的值。

为说明发生的情况,以下是该函数的可视化结果,该函数在颜色中查找值,然后获取与该颜色对应的值。

from matplotlib import pyplot as plt
import numpy as np

x = np.linspace(-0.1, 1.1, 10000)
y = [ find_value_in_colormap(plt.cm.viridis(x), plt.cm.viridis) for x in x]

fig, axes = plt.subplots(ncols=3, figsize=(12,4))
for ax in axes.ravel():
    ax.plot(x, x, label='identity: y = x')
    ax.plot(x, y, label='lookup, then reverse')
    ax.legend(loc='best')
axes[0].set_title('overall view')
axes[1].set_title('zoom near x=0')
axes[1].set_xlim(-0.02, 0.02)
axes[1].set_ylim(-0.02, 0.02)
axes[2].set_title('zoom near x=1')
axes[2].set_xlim(0.98, 1.02)
axes[2].set_ylim(0.98, 1.02)
plt.show()

overview plot

对于只有几种颜色的色图,曲线图可以显示一种颜色变为另一种颜色的确切位置。该图的颜色与x值相对应。

overview for tab10

答案 2 :(得分:0)

Hy unutbu,

感谢您的回复,我了解您解释的过程,并重现它。它工作得非常好,我使用它来反转温度网格中的红外摄像机镜头,因为使用GIMP可以轻松地重新制作/重塑照片以实现我的目的。

我能够通过相机镜头创建标量网格,这对我的任务非常有用。

我使用我可以使用GIMP + Sample a Gradient Along a Path创建的调色板文件。 我选择原始图片的颜色条,将其转换为调色板,然后导出为十六进制颜色序列。 我读了这个调色板文件来创建一个由温度样本标准化的色彩图,用作代码簿。 我读取原始图像并使用矢量量化将颜色反转为值。 通过在温度样本数组中使用代码簿索引作为索引过滤器,我稍微改善了代码的pythonic样式,并应用了一些过滤器来平滑我的结果。

from numpy import linspace, savetxt
from matplotlib.colors import Normalize, LinearSegmentedColormap
from  scipy.cluster.vq import vq

# sample the values to find from colorbar extremums
vmin = -20.
vmax = 120.
precision = 1.

resolution = 1 + vmax-vmin/precision
sample = linspace(vmin,vmax,resolution)

# create code_book from sample
cmap = LinearSegmentedColormap.from_list('Custom', hex_color_list)
norm = Normalize()
code_book = cmap(norm(sample))

# quantize colors
indices = vq(flat_image,code_book)[0]
# filter sample from quantization results **(improved)**
values = sample[indices]

savetxt(image_file_name[:-3]+'.csv',values ,delimiter=',',fmt='%-8.1f')

结果最终以.csv

导出

最重要的是创建一个代表性很好的调色板文件以获得良好的精度。我开始使用12种颜色和更多颜色获得良好的渐变(代码簿)。 此过程非常有用,因为有时相机镜头无法轻松且线性地转换为灰度。

感谢所有贡献者unutbu,Rob A,scipy社区;)

答案 3 :(得分:0)

如果我在测试期间不手动操作,则LinearSegmentedColormap不会给我相同的插值,所以我更喜欢使用自己的插值:

作为一个优势,因为我将代码集成到现有软件中,所以不需要matplotlib。

def codeBook(color_list, N=256):
    """
    return N colors interpolated from rgb color list
    !!! workaround to matplotlib colormap to avoid dependency !!!
    """
    # seperate r g b channel
    rgb = np.array(color_list).T
    # normalize data points sets
    new_x = np.linspace(0., 1., N)
    x = np.linspace(0., 1., len(color_list))
    # interpolate each color channel
    rgb = [np.interp(new_x, x, channel) for channel in rgb]
    # round elements of the array to the nearest integer.
    return np.rint(np.column_stack( rgb )).astype('int')