OpenCV 例程200篇 总目录
201. 图像的颜色空间转换
202. 查表快速替换(cv.LUT)
203. 伪彩色图像处理
204. 图像的色彩风格滤镜
205. 调节色彩平衡/饱和度/明度
文章目录
- 【youcans 的 OpenCV 例程200篇】202. 查表快速替换(cv.LUT)
- LUT 函数原型
- LUT 究竟是什么?
- 为什么要用 LUT?
- LUT 用来干什么?
- 例程 14.3:LUT 实现图像反转
- 例程 14.4:LUT 实现颜色空间缩减
- 例程 :LUT 实现分段线性灰度变换
【youcans 的 OpenCV 例程200篇】202. 查表快速替换(cv.LUT)
LUT 函数原型
函数 cv.LUT() 用来实现对图像中像素值的快速转换,称为查表函数(Look up table)。
cv.LUT(src, lut [, dst=None]) → dst
函数 cv.LUT() 根据查找表中的值,填充输出数组,由此实现对输入数组的数值转换。输出值与输入值的映射关系为:
dst(I)←lut[src(I)+d]d={0,src:cv_8U128,src:cv_8Sdst(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 相同
注意事项:
- 输入图像为多通道彩色图像时,查找表 lut 可以是单通道,则查找表适用于输入图像的所有通道;查找表 lut 也可以与输入图像的通道数量相同,则查找表的各通道分别适用于输入图像的对应通道。
- 函数 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[new]=(I[old]//32)∗32I[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 -> Grayh, 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) * 8timeEnd = 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 levelsgray32 = 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 levelsgray8 = 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 -> Grayh, 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) * 8timeEnd = 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 levelsgray32 = 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 levelsgray8 = 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学习课』 系列,持续更新中