【youcans 的 OpenCV 例程200篇】202. 查表快速替换(cv.LUT)

OpenCV 例程200篇 总目录


【youcans 的 OpenCV 例程200篇】202. 查表快速替换(cv.LUT)

LUT 函数原型

函数 cv.LUT() 用来实现对图像中像素值的快速转换,称为查表函数(Look up table)。

cv.LUT(src, lut [, dst=None]) → dst

函数 cv.LUT() 根据查找表中的值,填充输出数组,由此实现对输入数组的数值转换。输出值与输入值的映射关系为:

d s t ( I ) ← l u t [ s r c ( I ) + d ] d = { 0 , s r c : c v _ 8 U 128 , s r c : c v _ 8 S dst(I) \leftarrow lut[src(I) + d] \\ d = \begin{cases} 0,& src: cv\_8U \\ 128,& src: cv\_8S \end{cases} dst(I)lut[src(I)+d]d={ 0,128,src:cv_8Usrc:cv_8S

参数说明:

  • src:输入图像,nparray 数组,8位无符号/ 8位有符号整数格式
  • lut:查找表,256个元素
  • dst:输出图像,大小和通道数与 src 相同

注意事项:

  1. 输入图像为多通道彩色图像时,查找表 lut 可以是单通道,则查找表适用于输入图像的所有通道;查找表 lut 也可以与输入图像的通道数量相同,则查找表的各通道分别适用于输入图像的对应通道。
  2. 函数 cv.LUT() 本质上是查表替换,因此不仅可以用于灰度图像,也可以用于 RGB 彩色图像,还可以用于 HSV、YCrCb、LAB 等色彩空间的图像。

LUT 究竟是什么?

LUT 函数很简单,就是查表替换,用 lut 表中的值替换输入图像中对应的像素值。但还是需要详细解释这个查找表的内容和替换机制,否则很容易误解。

图像中每个像素的值称为像素值,灰度图像的像素值也称为灰度值。8 位灰度图像有 256 个灰度级,因此灰度值的取值范围是 [0,255]。

查找表 lut 是一个 256 个元素的一维数组,查找表中每个元素的值是新的像素值,用于替换像素值为该序号的像素的像素值。这句话很别扭,不好理解。

简单说,就是把输入图像中像素值为 0 的点用 lut[0] 替换,像素值为 i 的点用 lut[i] 替换,…,像素值为 255 的点用 lut[255] 替换。

因此,查找表就是一个简单的一对一或多对一的函数,定义了如何将原像素值转换为新的像素值。本质上查找表相当于一个字典,只是由于 key 对应于序号/地址可以被省略,因此只剩下一列 value。

输入图像为 8位有符号整数时,其像素值范围为 [-128,127],因此通过 d=128 进行调整。


为什么要用 LUT?

LUT 函数的核心在于查找表的映射关系。显然,这种映射关系是通过某种算法实现的,例如线性/非线性拉伸。

但是,既然已经有了映射关系,直接由输入图像的像素值计算输出值,不就得到输出图像了吗?为什么还要先由映射关系构造查找表,再用查找表进行像素替换?

是的,只要有了映射关系,就可以由输入图像的像素值计算输出值。但是,使用 LUT 查找表的速度远远快于对图像中逐个像素的映射变换。

对图像的每个像素,根据映射关系计算得到输出图像,要执行 height*width 次映射函数;而用 LUT 查找表,只要执行 256 次映射函数,再做 height*width 次替换操作。图像的像素点远远大于 256,因此相对于逐点计算,查找表用替换操作取代了大量的映射运算操作,极大地减小了计算量。

进一步地,查找表的替换操作是基于 numpy 的遍历查找和替换,而不是通过两重循环对逐个像素进行操作,进一步提高了运算速度。

LUT 用来干什么?

查找表在图像处理中主要用于像素点的映射运算,包括线性变换和非线性变换,处理速度极快。

查找表只能用于不涉及位置相关、邻域相关的操作,如:替换、取反、赋值、阈值、二值化、灰度变换、颜色缩减和直方图均衡化;不能用于位置相关、邻域相关的操作,如旋转、模糊。


例程 14.3:LUT 实现图像反转

图像反转(Invert)也称为反色变换,是像素颜色的逆转,将黑色像素点变白色,白色像素点变黑色,像素位置不变。

对于 8 位灰度图像,图像反转操作就是用 255 减掉像素值。

本例程以此比较循环操作与 LUT查表操作的运行速度。运行结果的差距是惊人的,虽然图像取反操作是最简单的运算,但用 for 循环实现的用时也竟然是 LUT 查表法的 1000倍。

    # 14.3 LUT 实现图像反转
    img = cv.imread("../images/imgGaia.tif")  # 读取彩色图像(BGR)
    h, w, ch = img.shape  # 图片的高度, 宽度 和通道数

    timeBegin = cv.getTickCount()
    imgInv = np.empty((w,h,ch), np.uint8)  # 创建空白数组
    for i in range(h):
        for j in range(w):
            for k in range(ch):
                imgInv[i][j][k] = 255 - img[i][j][k]
    timeEnd = cv.getTickCount()
    time = (timeEnd-timeBegin)/cv.getTickFrequency()
    print("图像反转(for 循环实现): {} s".format(round(time, 4)))

    timeBegin = cv.getTickCount()
    transTable = np.array([(255-i) for i in range(256)]).astype("uint8")
    invLUT = cv.LUT(img, transTable)
    timeEnd = cv.getTickCount()
    time = (timeEnd-timeBegin)/cv.getTickFrequency()
    print("图像反转(LUT 查表实现): {} s".format(round(time, 4)))

    plt.figure(figsize=(9, 6))
    plt.subplot(131), plt.title("img"), plt.axis('off')
    plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
    plt.subplot(132), plt.title("imgInv"), plt.axis('off')
    plt.imshow(cv.cvtColor(imgInv, cv.COLOR_BGR2RGB))
    plt.subplot(133), plt.title("invLUT"), plt.axis('off')
    plt.imshow(cv.cvtColor(invLUT, cv.COLOR_BGR2RGB))
    plt.tight_layout()
    plt.show()

运行结果:
图像反转(for 循环实现): 2.6839 s
图像反转(LUT 查表实现): 0.0027 s

在这里插入图片描述


例程 14.4:LUT 实现颜色空间缩减

颜色空间缩减是将图像的像素值除以某个参数,以得到较少的颜色种类,这是 LUT 的典型应用。

例如,8 位灰度图像对应着 255个灰度级,在某些印刷条件下可以将其缩减到 8个灰度级。这是一个简单的多对一的映射:
I [ n e w ] = ( I [ o l d ] / / 32 ) ∗ 32 I[new] = (I[old]//32) * 32 I[new]=(I[old]//32)32
通常的做法是循环遍历所有像素点,逐一按该映射公式进行计算。

使用查表函数,只要预先对所有灰度级 0-255 建立对应表 table = [0,…0,32,…32,…,224,…224],用 LUT 查表替换就可以实现。

在本例程中,通过 for 循环实现的用时是 LUT 查表法的 4300倍。

另一方面,32级与 256级灰度的显示效果并没有显著的区别,而使用 8级灰度时则出现了明显的颜色偏差。

    # 14.4 颜色空间缩减
    img = cv.imread("../images/imgLena.tif")  # 读取彩色图像(BGR)
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)  # BGR -> Gray
    h, w = img.shape[:2]  # 图片的高度, 宽度

    timeBegin = cv.getTickCount()
    imgGray32 = np.empty((w,h), np.uint8)  # 创建空白数组
    for i in range(h):
        for j in range(w):
            imgGray32[i][j] = (gray[i][j]//8) * 8
    timeEnd = cv.getTickCount()
    time = (timeEnd-timeBegin)/cv.getTickFrequency()
    print("灰度级缩减(for 循环实现): {} s".format(round(time, 4)))

    timeBegin = cv.getTickCount()
    table32 = np.array([(i//8)*8 for i in range(256)]).astype("uint8")  # 32 levels
    gray32 = cv.LUT(gray, table32)
    timeEnd = cv.getTickCount()
    time = (timeEnd-timeBegin)/cv.getTickFrequency()
    print("灰度级缩减(LUT 查表实现): {} s".format(round(time, 4)))

    table8 = np.array([(i//32)*32 for i in range(256)]).astype("uint8")  # 8 levels
    gray8 = cv.LUT(gray, table8)

    plt.figure(figsize=(9, 6))
    plt.subplot(131), plt.axis('off'), plt.title("gray-256"), plt.imshow(gray, cmap='gray')
    plt.subplot(132), plt.axis('off'), plt.title("gray-32"), plt.imshow(gray32, cmap='gray')
    plt.subplot(133), plt.axis('off'), plt.title("gray-8"), plt.imshow(gray8, cmap='gray')
    plt.tight_layout()
    plt.show()
    # 14.4 颜色空间缩减
    img = cv.imread("../images/imgLena.tif")  # 读取彩色图像(BGR)
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)  # BGR -> Gray
    h, w = img.shape[:2]  # 图片的高度, 宽度

    timeBegin = cv.getTickCount()
    imgGray32 = np.empty((w,h), np.uint8)  # 创建空白数组
    for i in range(h):
        for j in range(w):
            imgGray32[i][j] = (gray[i][j]//8) * 8
    timeEnd = cv.getTickCount()
    time = (timeEnd-timeBegin)/cv.getTickFrequency()
    print("灰度级缩减(for 循环实现): {} s".format(round(time, 4)))

    timeBegin = cv.getTickCount()
    table32 = np.array([(i//8)*8 for i in range(256)]).astype("uint8")  # 32 levels
    gray32 = cv.LUT(gray, table32)
    timeEnd = cv.getTickCount()
    time = (timeEnd-timeBegin)/cv.getTickFrequency()
    print("灰度级缩减(LUT 查表实现): {} s".format(round(time, 4)))

    table8 = np.array([(i//32)*32 for i in range(256)]).astype("uint8")  # 8 levels
    gray8 = cv.LUT(gray, table8)

    plt.figure(figsize=(9, 6))
    plt.subplot(131), plt.axis('off'), plt.title("gray-256"), plt.imshow(gray, cmap='gray')
    plt.subplot(132), plt.axis('off'), plt.title("gray-32"), plt.imshow(gray32, cmap='gray')
    plt.subplot(133), plt.axis('off'), plt.title("gray-8"), plt.imshow(gray8, cmap='gray')
    plt.tight_layout()
    plt.show()

运行结果:
灰度级缩减(for 循环实现): 0.8667 s
灰度级缩减(LUT 查表实现): 0.0002 s

在这里插入图片描述


例程 :LUT 实现分段线性灰度变换

在例程 1.50, 1.51 中,分别给出了用 for 循环和用 LUT 实现的分段线性拉伸变换的灰度变换方法和例程。

详见:【OpenCV 例程200篇】40. 图像分段线性灰度变换

在这里插入图片描述


【本节完】

版权声明:
youcans@xupt 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/125278730)
Copyright 2022 youcans, XUPT
Crated:2022-6-14
欢迎关注 『youcans 的 OpenCV 例程 200 篇』 系列,持续更新中
欢迎关注 『youcans 的 OpenCV学习课』 系列,持续更新中

猜你喜欢

转载自blog.csdn.net/youcans/article/details/125278730