有没有更快的方法将(BGR)OpenCV 图像转换为 CMYK?

Mat*_*ias 5 python algorithm opencv numpy image-processing

我有一个 OpenCV 图像,像往常一样在 BGR 颜色空间中,我需要将其转换为 CMYK。我在网上搜索但发现基本上只有以下方法(略有不同):

def bgr2cmyk(cv2_bgr_image):
    bgrdash = cv2_bgr_image.astype(float) / 255.0

    # Calculate K as (1 - whatever is biggest out of Rdash, Gdash, Bdash)
    K = 1 - numpy.max(bgrdash, axis=2)

    with numpy.errstate(divide="ignore", invalid="ignore"):
        # Calculate C
        C = (1 - bgrdash[..., 2] - K) / (1 - K)
        C = 255 * C
        C = C.astype(numpy.uint8)

        # Calculate M
        M = (1 - bgrdash[..., 1] - K) / (1 - K)
        M = 255 * M
        M = M.astype(numpy.uint8)

        # Calculate Y
        Y = (1 - bgrdash[..., 0] - K) / (1 - K)
        Y = 255 * Y
        Y = Y.astype(numpy.uint8)

    return (C, M, Y, K)
Run Code Online (Sandbox Code Playgroud)

这工作正常,但是,感觉相当慢 - 对于 800 x 600 像素的图像,在我的 i7 CPU 上大约需要 30 毫秒。对于相同的图像,类似阈值处理等的典型操作cv2只需要几毫秒,因此,由于这就是numpy我期望 CMYK 转换速度更快的原因。

然而,我还没有发现任何东西能让它变得更胖。通过 可以转换为 CMYK PIL.Image,但生成的通道看起来与上面列出的算法不同。

还有其他想法吗?

Chr*_*itz 3

您应该做几件事:

  • 动摇数学
  • 尽可能使用整数数学
  • 优化超出 numpy 的范围
震撼数学

给定

RGB' = RGB / 255
K = 1 - max(RGB')
C = (1-K - R') / (1-K)
M = (1-K - G') / (1-K)
Y = (1-K - B') / (1-K)
Run Code Online (Sandbox Code Playgroud)

你会看到你能分解出什么。

RGB' = RGB / 255
J = max(RGB')
K = 1 - J
C = (J - R') / J
M = (J - G') / J
Y = (J - B') / J
Run Code Online (Sandbox Code Playgroud)
整数数学

不要[0,1]对这些计算进行归一化。可以max()对整数进行。差异也可以。可以完全用整数数学来K计算。

J = max(RGB)
K = 255 - J
C = 255 * (J - R) / J
M = 255 * (J - G) / J
Y = 255 * (J - B) / J
Run Code Online (Sandbox Code Playgroud)
努巴
import numba
Run Code Online (Sandbox Code Playgroud)

Numba 将优化该代码,而不仅仅是使用 numpy 库例程。它还将按照指示进行并行化。选择numpy错误模型并允许fastmath将导致除以零,从而不会引发异常或警告,但也会使数学运算更快一些。

这两种变体都明显优于普通的 python/numpy 解决方案。这很大程度上是由于更好地使用了 CPU 寄存器缓存,而不是像 numpy 那样使用中间数组。

第一个变体:~1.9 ms

@numba.njit(parallel=True, error_model="numpy", fastmath=True)
def bgr2cmyk_v4(bgr_img):
    bgr_img = np.ascontiguousarray(bgr_img)
    (height, width) = bgr_img.shape[:2]
    CMYK = np.empty((height, width, 4), dtype=np.uint8)
    for i in numba.prange(height):
        for j in range(width):
            B,G,R = bgr_img[i,j] 
            J = max(R, G, B)
            K = np.uint8(255 - J)
            C = np.uint8(255 * (J - R) / J)
            M = np.uint8(255 * (J - G) / J)
            Y = np.uint8(255 * (J - B) / J)
            CMYK[i,j] = (C,M,Y,K)
    return CMYK
Run Code Online (Sandbox Code Playgroud)

感谢 Cris Luengo 指出了进一步重构的潜力(退出255/J),从而产生了第二种变体。大约需要1.6 毫秒

@numba.njit(parallel=True, error_model="numpy", fastmath=True)
def bgr2cmyk_v5(bgr_img):
    bgr_img = np.ascontiguousarray(bgr_img)
    (height, width) = bgr_img.shape[:2]
    CMYK = np.empty((height, width, 4), dtype=np.uint8)
    for i in numba.prange(height):
        for j in range(width):
            B,G,R = bgr_img[i,j] 
            J = np.uint8(max(R, G, B))
            Jinv = np.uint16((255*256) // J) # fixed point math
            K = np.uint8(255 - J)
            C = np.uint8(((J - R) * Jinv) >> 8)
            M = np.uint8(((J - G) * Jinv) >> 8)
            Y = np.uint8(((J - B) * Jinv) >> 8)
            CMYK[i,j] = (C,M,Y,K)
    return CMYK
Run Code Online (Sandbox Code Playgroud)

这个定点数学会导致四舍五入。对于舍入到最接近的值,表达式必须是((J - R) * Jinv + 128) >> 8。这会花费更多的时间(约 1.8 毫秒)。

还有什么?

我认为 numba/LLVM 没有在这里应用 SIMD。一些调查显示循环矢量化器不喜欢它被要求考虑的任何实例。

OpenCL内核可能会更快。OpenCL 可以在 CPU 上运行。

Numba 还可以使用CUDA