如何使用Python将图像所有像素值转换为特定范围

3
我有一张RGB图像,其中有12种不同的颜色,但我事先不知道这些颜色(像素值)。我想将所有像素值从0到11进行转换,每个值都代表原始RGB图像中的一种唯一颜色。
例如,所有[230,100,140]都转换为[0,0,0],所有[130,90,100]都转换为[0,0,1],以此类推...所有[210,80,50]都转换为[0,0,11]。

所以首先您需要构建一组颜色,然后将它们映射到一个索引上吗? - Willem Van Onsem
@Miki:但这并没有为像素分配索引。在这里,您选择了12种颜色。但我们已经知道图像只包含12种颜色。 - Willem Van Onsem
你想将一个范围转换为另一个范围。请查看此链接:https://dev59.com/VHNA5IYBdhLWcg3wgeSk - zindarod
有没有可用的GetPixel() / SetPixel()函数? - Evan Carslake
1
你不应该将一个标量值分配给这些颜色三元组中的任意一个,比如[230,100,140] 转换成 0 - Divakar
@Divakar,那样也可以。 - Fateh Singh
2个回答

2

快速而简单的应用程序。许多方面可以改进,特别是逐像素地处理整个图像不太符合numpy和opencv的要求,但我懒得记住如何阈值化和替换RGB像素。

import cv2
import numpy as np

#finding unique rows
#comes from this answer : https://dev59.com/cWoy5IYBdhLWcg3wa9ff
def unique_rows(a):
    a = np.ascontiguousarray(a)
    unique_a = np.unique(a.view([('', a.dtype)]*a.shape[1]))
    return unique_a.view(a.dtype).reshape((unique_a.shape[0], a.shape[1]))

img=cv2.imread(your_image)

#listing all pixels
pixels=[]
for p in img:
    for k in p:
        pixels.append(k)

#finding all different colors
colors=unique_rows(pixels)

#comparing each color to every pixel
res=np.zeros(img.shape)
cpt=0
for color in colors:
    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            if (img[i,j,:]==color).all(): #if pixel is this color
                res[i,j,:]=[0,0,cpt] #set the pixel to [0,0,counter]
    cpt+=1

1
您可以使用一些技巧来使用np.unique
import numpy as np

def safe_method(image, k):
    # a bit of black magic to make np.unique handle triplets
    out = np.zeros(image.shape[:-1], dtype=np.int32)
    out8 = out.view(np.int8)
    # should really check endianness here
    out8.reshape(image.shape[:-1] + (4,))[..., 1:] = image
    uniq, map_ = np.unique(out, return_inverse=True)
    assert uniq.size == k
    map_.shape = image.shape[:-1]
    # map_ contains the desired result. However, order of colours is most
    # probably different from original
    colours = uniq.view(np.uint8).reshape(-1, 4)[:, 1:]
    return colours, map_

然而,如果像素数量远大于颜色数量,以下启发式算法可能会大大提高速度。它试图找到一种廉价的哈希函数(例如仅查看红色通道),如果成功则使用它创建查找表。如果失败,则退回到上述安全方法。
CHEAP_HASHES = [lambda x: x[..., 0], lambda x: x[..., 1], lambda x: x[..., 2]]

def fast_method(image, k):
    # find all colours
    chunk = int(4 * k * np.log(k)) + 1
    colours = set()
    for chunk_start in range(0, image.size // 3, chunk):
        colours |= set(
            map(tuple, image.reshape(-1,3)[chunk_start:chunk_start+chunk]))
        if len(colours) == k:
            break
    colours = np.array(sorted(colours))
    # find hash method
    for method in CHEAP_HASHES:
        if len(set(method(colours))) == k:
            break
    else:
        safe_method(image, k)
    # create lookup table
    hashed = method(colours)
    # should really provide for unexpected colours here
    lookup = np.empty((hashed.max() + 1,), int)
    lookup[hashed] = np.arange(k)
    return colours, lookup[method(image)]

测试和计时:

from timeit import timeit

def create_image(k, M, N):
    colours = np.random.randint(0, 256, (k, 3)).astype(np.uint8)
    map_ = np.random.randint(0, k, (M, N))
    image = colours[map_, :]
    return colours, map_, image

k, M, N = 12, 1000, 1000

colours, map_, image = create_image(k, M, N)

for f in fast_method, safe_method:
    print('{:16s} {:10.6f} ms'.format(f.__name__, timeit(
        lambda: f(image, k), number=10)*100))
    rec_colours, rec_map_ = f(image, k)
    print('solution correct:', np.all(rec_colours[rec_map_, :] == image))

示例输出(12种颜色,1000x1000像素):

fast_method        3.425885 ms
solution correct: True
safe_method       73.622813 ms
solution correct: True

safe_method() 已经运行成功。fast_method() 在第一个 for 循环中抛出错误 TypeError: 'numpy.ndarray' object is not callable - Fateh Singh
@FatehSingh 嗯,你有没有偶然影响到内置函数?因为据我所见,在那个循环中唯一的函数调用是 range, set, map, tuplelen(我认为我们可以排除 image.reshape)。请检查其中是否有一个是数组。如果是的话,你应该将该数组重命名为其他名称。你可以使用 import builtins 来获取内置函数,例如 map = builtins.map - Paul Panzer
没错,它可以工作了,是名称冲突导致了问题。谢谢。 - Fateh Singh

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