如何反转颜色映射图像?
我有一个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
答案 0 :(得分:8)
可能有更好的方法来做到这一点;我不确定。
如果您阅读help(cm.jet)
,您将看到用于将区间[0,1]中的值映射到RGB 3元组的算法。你可以用一点纸和铅笔计算公式来反转定义映射的分段线性函数。
然而,有许多问题使得纸和铅笔解决方案有些缺乏吸引力:
这是一个很费力的代数,而且 该解决方案特定于cm.jet。 你必须再做所有这些工作 如果你改变了颜色图。如何自动解决这些代数方程很有意思,但这不是我知道如何解决的问题。
通常,颜色图可能不是 可逆的(可能有多个值) 被映射到相同的颜色)。在里面 cm.jet的情况,值在0.11之间 和0.125都映射到RGB 例如,3元组(0,0,1)。因此,如果 你的图像包含纯蓝色 像素,真的没办法 告诉它是否来自0.11的值 或者值,比方说,0.125。
由于非唯一性问题以及投影/插值问题,您可能会遇到许多可能的解决方案。以下只是一种可能性。
以下是解决唯一性和投影/插值问题的一种方法:
创建一个充当“代码簿”的gradient
。 gradient
是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:
(原始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()
对于只有几种颜色的色图,曲线图可以显示一种颜色变为另一种颜色的确切位置。该图的颜色与x值相对应。
答案 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')