【问题标题】:Vectorizing custome RGB -> Grayscale conversion矢量化自定义 RGB -> 灰度转换
【发布时间】:2020-08-19 02:28:09
【问题描述】:

如标题中所述,我想进行从 RGB 到灰度的非常具体的转换。

我有一堆可能看起来像这样的图像:

我想把它们转换成这样的图像 .

现在你可能想知道为什么我不只是使用 opencv 的内置函数。原因是我需要将 RGB 图像的每种颜色映射到灰度中的特定强度值,这并不难,因为我只有六种颜色。

Red, rgb(255,0,0)           ->  25
Brown, rgb(165,42,42)       ->  120
Light Blue, rgb(0,255,255)  ->  127
Green, rgb(127,255,0)       ->  50
Yellow, rgb(255,255,255)    ->  159
Purple, rgb(128, 0, 128)    ->  90

现在我已经创建了一个数组,其中包含一些包含这些映射的对象,并且我只是迭代我的图像以分配新的颜色代码。然而,这是非常缓慢的,我希望在完成所有图像之前长出华丽的胡须(我也想知道这一点以用于学习目的)。这是我目前运行速度超慢的代码和映射对象:

colorMapping = [ColorMapping(RGB=[255, 0, 0], Grayscale=25),
 ColorMapping(RGB=[165, 42, 42], Grayscale=120), ... ]

def RGBtoGray(RGBimg, colorMapping):
    RGBimg = cv2.cvtColor(RGBimg, cv2.COLOR_BGR2RGB)
    row = RGBimg.shape[0]
    col = RGBimg.shape[1]
    GRAYimg = np.zeros((row, col))
    for x in range(0,row):
        for y in range(0,col):
            pixel = RGBimg[x,y,:]
            for cm in colorMapping:
                if np.array_equal(pixel, np.array(cm.RGB)):
                    GRAYimg[x,y] = cm.Grayscale    
    return GRAYimg   

我很高兴收到有关使用内置库或改进此代码计算的任何建议。 颜色图是从一个 json 文件中读取的,它作为一个自动化步骤起作用,因为我必须至少对两批具有不同编码的图像执行此操作。

【问题讨论】:

  • 转换为灰度后,6 种 RGB 颜色最终具有不同的强度:76,79,179,188,255 and 53。由于它们是独一无二的,我们可以使用 2 个已经优化的步骤。首先,转换 RGB2GRAY,然后应用 LUT 重新映射强度。
  • @DanMašek 是的,这也是我最初的想法 :-) 但是 OP 的其他地图可能不太友好。
  • @MarkSetchell 好点。

标签: python-3.x performance numpy opencv


【解决方案1】:

这可能和其他任何事情一样简单。它确实对你的图像进行了 6 次传递,所以一些聪明的 Numpy 人可能知道更好的方法,但它会比你的循环快得多。

#!/usr/bin/env python3

import numpy as np
import cv2

# Load image
im = cv2.imread('blobs.png')

# Make output image
res = np.zeros_like(im[:,:,0])

res[np.all(im == (0, 0, 255),   axis=-1)] = 25
res[np.all(im == (42,42,165),   axis=-1)] = 120
res[np.all(im == (255,255,0),   axis=-1)] = 127
res[np.all(im == (0,255,127),   axis=-1)] = 50
res[np.all(im == (255,255,255), axis=-1)] = 159
res[np.all(im == (128,0,128),   axis=-1)] = 90

# Write image of just palette indices
cv2.imwrite('indices.png', res)

根据this 答案的启发,您可以通过将每个 RGB 三元组转换为单个 24 位数字,使其在 5 毫秒而不是 30 毫秒内运行。

#!/usr/bin/env python3

import numpy as np
import cv2
def remap2(im):
    # Make output image
    res = np.zeros_like(im[:,:,0])

    # Make a single 24-bit number for each pixel
    r = np.dot(im.astype(np.uint32),[1,256,65536]) 

    c0 =   0 +   0*256 + 255*65536
    c1 =  42 +  42*256 + 165*65536
    c2 = 255 + 255*256 +   0*65536
    c3 =   0 + 255*256 + 127*65536
    c4 = 255 + 255*256 + 255*65536
    c5 = 128 +   0*256 + 128*65536

    res[r == c0] = 25
    res[r == c1] = 120
    res[r == c2] = 127
    res[r == c3] = 50
    res[r == c4] = 159
    res[r == c5] = 90
    return res

# Load image
im = cv2.imread('blobs.png')
res = remap2(im)
cv2.imwrite('result.png',res)

【讨论】:

    【解决方案2】:

    方法#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)
    

    【讨论】:

    • 我尝试了方法一,它的工作速度比以前快了很多! (就像一对夫妇 100 次)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-29
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 2011-04-24
    相关资源
    最近更新 更多