如何将色彩图像反转为标量值?
我该如何反转一个颜色映射的图像?
我有一张二维图像,它用颜色来表示数据。我想把这张图像读进来,并“反转”这个颜色映射,也就是说,我想查找一个特定的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 个回答
嗨,unutbu,
感谢你的回复,我明白你解释的过程,并且已经成功复现了。这个方法效果很好,我用它来处理红外相机拍摄的温度网格,因为我可以很方便地用GIMP对图片进行重新处理和调整,以达到我的目的。
我能够从相机拍摄的图像中创建标量网格,这对我的工作非常有用。
我使用一个调色板文件,这个文件是我用GIMP和沿路径采样渐变功能创建的。我从原始图片中选取颜色条,将其转换为调色板,然后导出为十六进制颜色序列。我读取这个调色板文件,创建一个根据温度样本标准化的色彩图,用作代码书。我读取原始图像,使用向量量化将颜色反转为数值。我稍微改善了代码的python风格,通过在温度样本数组中使用代码书索引作为索引过滤器,并应用一些滤波器来平滑我的结果。
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社区;)
这里有一个更简单的方法,适用于很多颜色映射,比如 viridis
,但不适用于像 'jet' 这样的 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()
对于只有少数几种颜色的颜色映射,图表可以显示出一种颜色变换为另一种颜色的确切位置。图表的颜色与 x 值相对应。
可能还有更好的方法来解决这个问题,我不太确定。如果你查看一下 help(cm.jet)
,你会看到一个算法,它是用来把值从区间 [0,1] 映射到 RGB 三元组的。你可以用纸和铅笔,算出一些公式来反向计算这些定义映射的分段线性函数。
不过,用纸和铅笔的方法有几个问题,让人觉得不太方便:
这需要很多繁琐的代数运算,而且这个解决方案是针对 cm.jet 的。如果你换了颜色映射,就得重新做一遍这些工作。如何自动化解决这些代数方程是个有趣的问题,但我不知道怎么解决。
一般来说,颜色映射可能不是可逆的(也就是说,可能有多个值映射到同一种颜色)。以 cm.jet 为例,0.11 到 0.125 之间的值都会映射到 RGB 三元组 (0,0,1)。所以如果你的图像里有一个纯蓝色的像素,实际上很难判断它是来自 0.11 还是 0.125。
从 [0,1] 到三元组的映射在三维空间中是一条曲线。你图像中的颜色可能并不完全在这条曲线上。比如可能会有舍入误差。因此,任何实际的解决方案都必须能够插值或以某种方式将三维空间中的点投影到这条曲线上。
由于非唯一性的问题和投影/插值的问题,你提出的问题可能有很多解决方案。下面只是其中一种可能性。
这里有一种解决唯一性和投影/插值问题的方法:
创建一个 gradient
,它充当一个“代码本”。这个 gradient
是一个包含 cm.jet 颜色映射的 RGBA 四元组的数组。gradient
中的颜色对应于从 0 到 1 的值。使用 scipy 的向量量化函数 scipy.cluster.vq.vq 将你图像中的所有颜色(mri_demo.png)映射到 gradient
中最近的颜色。由于颜色映射可能会将多个值映射到同一种颜色,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 的效果:
(原始的 mri_demo.png 有一个白色边框。由于白色不是 cm.jet 中的颜色,请注意 scipy.cluster.vq.vq
将白色映射到 gradient
代码本中最近的点,而这个点恰好是一个淡绿色。)