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


    OpenCV 例程200篇 总目录
    201. 图像的颜色空间转换
    202. 查表快速替换(cv.LUT)
    203. 伪彩色图像处理
    204. 图像的色彩风格滤镜
    205. 调节色彩平衡/饱和度/明度


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

    LUT 函数原型

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

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

    函数 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 =

    {0,src:cv_8U128,src:cv_8S
    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    运行结果:
    图像反转(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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
        # 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    运行结果:
    灰度级缩减(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学习课』 系列,持续更新中

  • 相关阅读:
    基于Matlab实现元胞自动机(CA)
    5G邻近通信安全研究
    hadoop集群安装并配置
    树莓派学习笔记--串口通信(配置硬件串口进行通信)
    物体结构图,快速图解物体内部结构
    error while loading shared libraries: libc.so.6 误删除libc.so.6急救办法,
    多用户对应多租户解决方案
    解决ubuntu报错:No such file or directory
    [附源码]Python计算机毕业设计SSM健身房管理系统(程序+LW)
    乐信—高级Java开发工程师二面(偏业务)
  • 原文地址:https://blog.csdn.net/youcans/article/details/125278730