方法#1
这是一个基于1D 转换+np.searchsorted 的矢量化,灵感来自this post -
def map_colors(img, colors, vals, invalid_val=0):
s = 256**np.arange(3)
img1D = img.reshape(-1,img.shape[2]).dot(s)
colors1D = colors.reshape(-1,img.shape[2]).dot(s)
sidx = colors1D.argsort()
idx0 = np.searchsorted(colors1D, img1D, sorter=sidx)
idx0[idx0==len(sidx)] = 0
mapped_idx = sidx[idx0]
valid = colors1D[mapped_idx] == img1D
return np.where(valid, vals[mapped_idx], invalid_val).reshape(img.shape[:2])
示例运行 -
# Mapping colors array
In [197]: colors
Out[197]:
array([[255, 0, 0],
[165, 42, 42],
[ 0, 255, 255],
[127, 255, 0],
[255, 255, 255],
[128, 0, 128]])
# Mapping values array
In [198]: vals
Out[198]: array([ 25, 120, 127, 50, 155, 90])
# Input 3D image array
In [199]: img
Out[199]:
array([[[255, 255, 255],
[128, 0, 128],
[255, 0, 0],
[127, 255, 0]],
[[127, 255, 0],
[127, 255, 0],
[165, 42, 42],
[ 0, 0, 0]]]) # <= one color absent in mappings
# Output
In [200]: map_colors(img, colors, vals, invalid_val=0)
Out[200]:
array([[155, 90, 25, 50],
[ 50, 50, 120, 0]])
我们可以对映射进行预排序,从而摆脱 searchsorted 所需的排序,这将进一步提高性能 -
def map_colors_with_sorting(img, colors, vals, invalid_val=0):
s = 256**np.arange(3)
img1D = img.reshape(-1,img.shape[2]).dot(s)
colors1D = colors.reshape(-1,img.shape[2]).dot(s)
sidx = colors1D.argsort()
colors1D_sorted = colors1D[sidx]
vals_sorted = vals[sidx]
idx0 = np.searchsorted(colors1D_sorted, img1D)
idx0[idx0==len(sidx)] = 0
valid = colors1D_sorted[idx0] == img1D
return np.where(valid, vals_sorted[idx0], invalid_val).reshape(img.shape[:2])
方法#2
我们可以使用一个映射数组,当被1D 转换的颜色索引时,我们会直接得到最终的“灰度”图像,如下所示 -
def map_colors_with_mappingar_solution(img):
# Edit the custom colors and values here
colors = np.array([
[ 0, 0, 255],
[ 42, 42, 165],
[255, 255, 0],
[ 0, 255, 127],
[255, 255, 255],
[128, 0, 128]], dtype=np.uint8) # BGR format
vals = np.array([25, 120, 127, 50, 155, 90], dtype=np.uint8)
return map_colors_with_mappingar(img, colors, vals, 0)
def map_colors_with_mappingar(img, colors, vals, invalid_val=0):
s = 256**np.arange(3)
img1D = img.reshape(-1,img.shape[2]).dot(s)
colors1D = colors.reshape(-1,img.shape[2]).dot(s)
N = colors1D.max()+1
mapar = np.empty(N, dtype=np.uint8)
mapar[colors1D] = vals
mask = np.zeros(N, dtype=bool)
mask[colors1D] = True
valid = img1D < N
valid &= mask[img1D]
out = np.full(len(img1D), invalid_val, dtype=np.uint8)
out[valid] = mapar[img1D[valid]]
return out.reshape(img.shape[:2])
当您增加自定义颜色的数量时,这应该可以很好地扩展。
让我们为给定的示例图像计时 -
# Read in sample image
In [360]: im = cv2.imread('blobs.png')
# @Mark Setchell's solution
In [362]: %timeit remap2(im)
7.45 ms ± 105 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# Method2 from this post
In [363]: %timeit map_colors_with_mappingar_solution(im)
6.76 ms ± 46.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
进一步的性能。提升
更进一步,我们可以以更高效的方式进行一维缩减,从而实现进一步的性能。提升,就像这样 -
# https://stackoverflow.com/a/57236217/ @tstanisl
def scalarize(x):
# compute x[...,2]*65536+x[...,1]*256+x[...,0] in efficient way
y = x[...,2].astype('u4')
y <<= 8
y +=x[...,1]
y <<= 8
y += x[...,0]
return y
def map_colors_with_mappingar(img, colors, vals, invalid_val=0):
img1D = scalarize(img)
colors1D = scalarize(colors)
N = colors1D.max()+1
mapar = np.empty(N, dtype=np.uint8)
mapar[colors1D] = vals
mask = np.zeros(N, dtype=bool)
mask[colors1D] = True
valid = img1D < N
valid &= mask[img1D]
out = np.full(img1D.shape, invalid_val, dtype=np.uint8)
out[valid] = mapar[img1D[valid]]
return out
# On given sample image
In [10]: %timeit map_colors_with_mappingar_solution(im)
5.45 ms ± 143 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)