数字图像处理基础

参加完Kaggle比赛了,虽然做的是图像分割的题目,但是对于图像的基本知识,我还是很欠缺,所以在这里进行一个总结。

首先,这里先说明了颜色的三要素:色调(色相$H$)、饱和度(纯度$S$)和明度$V$,也可以是色调(色相$H$)、饱和度(纯度$S$)和亮度$L$。在下面可以用到。色调和饱和度一起称为色度。

RGB模型

简介

三原色光模式(RGB color model),又称RGB颜色模型或红绿蓝颜色模型,是一种加色模型(与之相对是减色模型),将红(Red)、绿(Green)、蓝(Blue)三原色的色光以不同的比例相加,以合成产生各种色彩光。(因为人类视觉感色细胞的原因,红绿蓝成为三原色合成色彩的基础)。

RGB是一种依赖于设备的颜色空间:不同设备对特定RGB值的检测和重现都不一样,因为颜色物质(荧光剂或者染料)和它们对红、绿和蓝的单独响应水平随着制造商的不同而不同,甚至是同样的设备不同的时间也不同。为了不同的设备显示结果差异不是很大,制造商需要屏幕显示进行校准。

另外需要注意的是,三原色的原理不是出于物理原因,而是由于生理原因造成的。也就是说使用三原色并不足以重现所有的色彩,准确地说法应该是 ”将三原色光以不同的比例复合后,对人的眼睛可以形成与各种频率的可见光等效的色觉。” 例如,红光与绿光按某种比例复合,对人类三种锥状细胞刺激后产生的色觉可与眼睛对单纯的黄光的色觉等效。但决不能认为红光与绿光按某种比例复合后生成黄光,或黄光是由红光和绿光复合而成的。

所有的彩色显示屏都是应用三原色光加色技术,以RGB三原色作为子像素构成一像素,由多个像素构成整个画面。三种原色光在每一像素中以0-255 ($2^8$)强度组合成从全黑色到全白色之间各种不同的颜色光,当前在计算机硬件中采取每一像素用24比特表示的方法,所以三种原色光各分到8比特,每一种原色的强度依照8比特的最高值28分为256个值。用这种方法可以组合16777216种颜色(这里注意颜色三要素)。

注意从这里可以看出,RGB三个通道的像素值表示的是R、G、B的强度,这里的强度不同于HSV空间中的明度$V$,也不同于HSL空间的亮度$L$,不要搞混了。另外,这里所说的16777216种颜色,因为颜色有三要素,所以这三个通道的组合可以组合出不同色调$H$、饱和度$S$、明度$V$(亮度$L$)的色彩。

由于gamma校正,在计算机显示设备上的颜色输出的强度通常不是直接正比于在图象文件中R, G和B像素值。就是说,即使值0.5非常接近于0到1.0(完全强度)的一半,计算机显示器在显示 (0.5, 0.5, 0.5)时候的光强度通常(在标准2.2-gamma CRT/LCD上)是在显示 (1.0, 1.0, 1.0)时候的大约22%,而不是50%。

通过三元素相加模型,可以组合出各个颜色的光,但是人类对于暗部信息比较敏感,所以显示器会经过gamma校正,让人眼看起来更加舒服。

JPG等图像格式,每像素为24位编码的RGB值,使用三个8位无符号整数(0到255)表示红色、绿色和蓝色的强度。这里的RGB三个值与HSV(色相、饱和度、明度)或HSL(色相、饱和度、亮度)有一一对应关系。使用每原色8-比特的全值域RGB可以有256级别的白-灰-黑深浅变化,255个级别的红色、绿色和蓝色(和它们的等量混合)的深浅变化,但是其他色相的深浅变化要少一些。由于gamma校正,对于显示器而言,256级别不表示同等间隔的强度。

RGB空间局限性

RGB 颜色空间利用三个颜色分量的线性组合来表示颜色,任何颜色都与这三个分量有关,而且这三个分量是高度相关的,所以连续变换颜色时并不直观,想对图像的颜色进行调整需要更改这三个分量才行。

自然环境下获取的图像容易受自然光照、遮挡和阴影等情况的影响,即对亮度比较敏感。而 RGB 颜色空间的三个分量都与亮度密切相关,即只要亮度改变,三个分量都会随之相应地改变,而没有一种更直观的方式来表达。

另外,人眼对于这三种颜色分量的敏感程度是不一样的。在单色中,人眼对红色最不敏感,蓝色最敏感,所以 RGB 颜色空间是一种均匀性较差的颜色空间。如果颜色的相似性直接用欧氏距离来度量,其结果与人眼视觉会有较大的偏差。对于某一种颜色,我们很难推测出较为精确的三个分量数值来表示。

所以,RGB 颜色空间适合于显示系统,却并不适合于图像处理。

HSL、HSV、HSB模型

HSL和HSV都是一种将RGB色彩模型中的点在圆柱坐标系中的表示法。这两种表示法试图做到比基于笛卡尔坐标系的几何结构RGB更加直观。

HSL即色相、饱和度、亮度(英语:Hue, Saturation, Lightness)。HSV即色相、饱和度、明度(英语:Hue, Saturation, Value),又称HSB,其中B即英语:Brightness。

  • 色相($H$)是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等。它是光波混合中与主波长有关的属性注意灰度图像是没有色相的概念的。
  • 饱和度($S$)是指色彩的纯度或者某种颜色混合白光的数量。饱和度越高色彩越纯,低则逐渐变灰,最终变为白色,取0-100%的数值。饱和度为0时,图像就为灰度图像
  • 明度($V$)可以理解为某种颜色混入黑色的数量。取0-100%。
  • 亮度($L$)是一种主观的描述子,实际上它是不可度量的。它体现了无色的强度概念,并且是描述彩色感觉的关键因子。取0-100%。

注意这几个英文缩写的区别。

如下图所示,HSL(a-d)和HSV(e-h)。上半部分(a、e):两者的3D模型截面。下半部分:将模型中三个参数的其中之一固定为常量,其它两个参数的图像。HSL和HSV二者都把颜色描述在圆柱坐标系内的点,这个圆柱的中心轴取值为自底部的黑色到顶部的白色而在它们中间的是灰色(所以对于灰度图只对应于圆柱的中心轴),绕这个轴的角度对应于“色相”,到这个轴的距离对应于“饱和度”,而沿着这个轴的高度对应于“亮度”、“色调”或“明度”。

img

HSV

在 RGB 中 颜色由三个值共同决定,比如黄色为即 (255,255,0);在HSV中,黄色只由一个值决定,$Hue=60$即可。当$Hue=60$时,HSV 圆柱体的半边横截面如下图所示:

img

  • 其中水平方向表示饱和度,饱和度表示颜色接近光谱色的程度。饱和度越高,说明颜色越深,越接近光谱色饱和度越低,说明颜色越浅,越接近白色。饱和度为0表示灰度图。取值范围为0~100%,值越大,颜色越饱和。

  • 竖直方向表示明度,决定颜色空间中颜色的明暗程度,明度越高,表示颜色越明亮,范围是 0-100%。明度为0表示纯黑色(此时颜色最暗)。

可以通俗理解为:

  • 在Hue一定的情况下,饱和度减小,就是往光谱色中添加白色,光谱色所占的比例也在减小,饱和度减为0,表示光谱色所占的比例为零,导致整个颜色呈现白色。

  • 明度(value)减小,就是往光谱色中添加黑色,光谱色所占的比例也在减小,明度减为0,表示光谱色所占的比例为零,导致整个颜色呈现黑色。

HSV 对用户来说是一种比较直观的颜色模型。我们可以很轻松地得到单一颜色,即指定颜色角$H$,并让$V=S=1$,然后通过向其中加入黑色和白色来得到我们需要的颜色。增加黑色可以减小$V$而$S$不变,同样增加白色可以减小$S$而$V$不变。例如,要得到深蓝色,$V=0.4, S=1, H=240$度。要得到浅蓝色,$V=1, S=0.4 , H=240$度。

HSL 、HSB

HSL 中的 $L$ (Lightness)分量为亮度,亮度为100,表示白色,亮度为0,表示黑色;HSV 中的 $V$(value,Brightness) 分量为明度,明度为100,表示光谱色,明度为0,表示黑色。

如下图所示,更加明显的可以见到明度和亮度的区别,在圆柱体外围是纯色(红黄绿蓝紫…)。HSL中,这圈纯色位于亮度(L)等于1/2的部位,而在HSV中是在明度(Value)等于1的部位。

imgimg

提取白色物体时,使用 HSL 更方便,因为 HSV 中的Hue里没有白色,白色需要由$S$和$V$共同决定($S=0, V=100$)。而在 HSL 中,白色仅由亮度$L$一个分量决定。所以检测白色时使用 HSL 颜色空间更准确。

OpenCV例子

在这里,我们举一个OpenCV的例子,功能为查看图片中任一点的像素BGR值、灰度值、HSV值以及HSL值。

在 OpenCV 中 HSV 三个分量的范围为:

  • H = [0,179]
  • S = [0,255]
  • V = [0,255]

在 OpenCV 中 HSL 三个分量的范围为:

  • H = [0,179]
  • L = [0,255]
  • S = [0,255]

代码为:

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
# -*- coding:utf-8 -*-

import cv2

img = cv2.imread('lena.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
hls = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)

def mouse_click(event, x, y, flags, para):
if event == cv2.EVENT_LBUTTONDOWN: # 左边鼠标点击
print('PIX:', x, y)
# 因为OpenCV组织图片为[W,h,C],而得到的坐标为[h,w],坐标均从左上角开始
# 所以 x与y 要颠倒一下
print("BGR:", img[y, x])
print("GRAY:", gray[y, x])
print("HSV:", hsv[y, x])
print("HLS:", hls[y, x])
print('\n')

if __name__ == '__main__':
cv2.namedWindow("img")
cv2.setMouseCallback("img", mouse_click)
while True:
cv2.imshow('img', img)
if cv2.waitKey() == ord('q'):
break
cv2.destroyAllWindows()

当我们点击图像中一个点的时候,结果如下:

1
2
3
4
5
PIX: 30 220
BGR: [33 30 46]
GRAY: 35
HSV: [174 89 46]
HLS: [174 38 54]

注意在OpenCV中,像素值是以BGR排列的,对于HLS: [174 38 54],我们显示器是无法显示HSL颜色空间的,只能以RGB颜色空间显示。当我们输入R=38,G=54,B=174时,显示的结果如下。当我们将上面代码cv2.imshow('img', img)改为cv2.imshow('img', hls)的时候,该像素点显示就是为蓝色。

1568085099796

所以我们得到结论,显示器显示的颜色空间为RGB空间,即使你使用OpenCV将RGB空间转换为了HLS空间,显示器会将转换的结果(像素值)当做RGB各个通道的值,然后渲染出来。所以我们看到的显示器显示出来的HLS空间结果是以HLS空间像素值放在RGB空间显示出来的。

颜色空间转换

RGB和CMYK分别是加法原色和减法原色模型,以原色组合的方式定义颜色,而HSV以人类更熟悉的方式封装了关于颜色的信息:“这是什么颜色?深浅如何?明暗如何?”

从RGB到HSL的转换

当图像为彩色图像时,设$ (r, g, b)$分别是一个颜色的红、绿和蓝坐标,它们的值是在0到1之间的实数。设$max$等价于$r, g$和$b$中的最大者。设$min$等于这些值中的最小者。要找到在HSL空间中的$ (h, s, l)$值,这里的$h ∈ [0, 360)$度是角度的色相角,而$s, l ∈ [0,1]$是饱和度和亮度,计算为:

当图像为灰度图像时,虽然它可能存储的时候是RGB三通道的值,此时$R=G=B$。此时,没有色相$h$的概念,$s=0$即饱和度等于0,$l=R=G=B$。

例题:现在有一个颜色 $(R: 0.1, G: 0.2, B: 0.5)$,这是一个比较暗的、略偏绿的蓝色。所以它的色相会略小于 240 度(具体值是 225 度),亮度 $L = (0.1 + 0.5) / 2 = 0.3$,饱和度分子 $C = 0.5 - 0.1 = 0.4$,分母为 0.6,所以饱和度为 $0.4 / 0.6 ≈ 0.67$。

  • 调整色相:由于亮度、饱和度都只与 RGB 中的最大、最小值相关,所以若要仅调整色相,就要调整绿色 G 的值(因为这里最大值为B,最小值为R,若调节了这两个值,就会改变亮度和饱和度,做不到仅调整色相)。增大 G 的值会让色相更偏向绿色,即减小,G = B 时色相最小,为 180 度;反之,减小 G 则会让色相增大,G = R 时色相最大,为 240 度。

  • 调整亮度:要调整亮度,主要靠调整值最大和最小的 B 和 R,但要注意保持色相和饱和度不变。当 $L < 0.5$ 时,饱和度 S 的公式可以简化为 $(M - m) / (M + m)$。为了保持饱和度不变,B 和 R 要同比例增减,增时亮度增加,减时亮度降低。为了保持色相不变,G 也要同比例增减。

  • 调整饱和度:同样,调整饱和度靠的也是 B 和 R。为了保持亮度不变,其中一个增加多少,另一个就要减小多少。增大 B、减小 R 可以让饱和度增加;减小 B、增大 R 可以让饱和度降低。为了保持色相不变,也要调整 G 使它与 B、R 的差值之比与原来相同。

上面的各种调整都要注意不能超过 $0 <= R <= G <= B <= 1$ 的范围。一旦超过,调整方法也会改变。

从RGB到HSV的转换

HSL和HSV有同样的色相$h$定义,但是其他分量不同。

当图像为彩色图像时,HSV颜色的$s$和$v$的值定义如下:

当图像为灰度图像时,虽然它可能存储的时候是RGB三通道的值,此时$R=G=B$。此时,没有色相$h$的概念,$s=0$即饱和度等于0,$v=R=G=B$。

从HSL到RGB的转换

给定HSL空间中的 $(h, s, l)$值定义的一个颜色,带有$h$在指示色相角度的值域$[0, 360]$中,分别表示饱和度和亮度的s和l在值域$[0, 1]$中,相应在RGB空间中的$ (r, g, b)$三原色,带有分别对应于红色、绿色和蓝色的$r, g$和$b$也在值域$[0, 1]$中,它们可计算为:

当$s = 0$,则结果的颜色是非彩色的、或灰色的。在这个特殊情况,$r, g$和$b$都等于$l$。注意$h$的值在这种情况下是未定义的。

当$s ≠ 0$的时候,可以使用下列过程:

对于每个颜色向量$Color = (ColorR, ColorG, ColorB) = (r, g, b),$${\mbox{for each}}\,C\in \{R,G,B\}$

从HSV到RGB的转换

类似的,给定在HSV中$ (h, s, v)$值定义的一个颜色,带有如上的变化于0到360之间的$h$,和分别表示饱和度和明度的变化于0到1之间的$s$和$v$。在RGB空间中对应的 $(r, g, b)$三原色可以计算为($R,G,B$变化于0到1之间):

当$s = 0$,则结果的颜色是非彩色的、或灰色的。在这个特殊情况,$r, g$和$b$都等于$v$。注意$h$的值在这种情况下是未定义的。

当$s ≠ 0$的时候,可以使用下列过程:

对于每个颜色向量

灰度图

灰度图是一种常用的图,例如在医疗图像中,基本都是灰度图,因此了解灰度图的一些特性特别重要。这里进行一个总结。

RGB空间:当RGB空间每个通道的值都相同的时候,代表灰度图。如下图所示:当$R=G=B$的时候,即为对角线上的值,此时为灰度图。

img

例子:当我们有一张gray.png图片,使用下面代码得到其通道数。可以看到该图像通道数为1。从这里可以看出,png或者jpg图片不一定存在三个通道的值,也有可能只保存了一个通道

1
2
3
4
5
6
7
8
9
import cv2
img = cv2.imread('gray.png')
img_gray = cv2.imread('gray.png', cv2.IMREAD_GRAYSCALE)

from PIL import Image
im= Image.open('gray.png')
print(im.getbands())

out:('L',)

对上面程序的输出结果进行分析:

  • 在写OpenCV程序时,发现通过img = cv2.imread('gray.png')的方式读入的灰度图片都是3通道,并且每个通道都完全相同

  • 为了正确地读入灰度图像,需要通过img_gray = cv2.imread('gray.png', cv2.IMREAD_GRAYSCALE)或者img_gray = cv2.imread('gray.png', 0)的方式来实现。

当图像为三通道的彩色图时,使用cv2.IMREAD_GRAYSCALE或者0参数会使用算法将RGB转换为单通道的灰度图。

HSV空间:注意$H$维度上是没有灰度概念的,如下图所示。灰度图处于中心轴上,此时饱和度$S$为0,色相$H$无定义,而$V=R=G=B$。

img

HSL空间:注意$H$维度上是没有灰度概念的,如下图所示。灰度图处于中心轴上,此时饱和度$S$为0,色相$H$无定义,而$L=R=G=B$。

img

这同时也说明了,对于灰度图,不管是什么变换,在数值上均是改变的像素值,物理上变换的是亮度或者说是明度。灰度图是没有饱和度的,不能对其进行饱和度变换。

图像变换

直方图均衡化

当图像为灰度图的时候,像素值表示亮度值$L$或者说是明度值$V$或者说是灰度值,没有色调$H$的概念,饱和度$S=0$。所以灰度图的直方图均衡化只能对亮度$L$或者明度$V$进行均值化。但是又这两者均等于像素值,所以从数值上来看,是对像素值进行均值化。

当图像为彩色图的时候,因为具有三个通道的值,若分别对三个通道的像素值进行均值化,这将产生不正确的彩色。可以将图像转换到HSI空间,保持$H$不变,对亮度$I$进行均值化,这叫做亮度直方图均衡化处理。

gamma变换

当图像为灰度图的时候,像素值表示亮度值$L$或者说是明度值$V$或者说是灰度值,没有色调$H$的概念,饱和度$S=0$。所以灰度图的gamma变换只能对亮度$L$或者明度$V$进行变换。但是又这两者均等于像素值,所以从数值上来看,是对像素值进行均值化。

当图像为彩色图的时候,因为具有三个通道的值,可以分别对三个通道的像素值进行gamma变换。但不仅会改变亮度,还会改变彩色图像中红、绿、蓝的比例,因此如何对彩色图像进行gamma变换,也是数字图像处理研究的重要内容。维基百科中指出,实际上,对彩色分量RGB分别做均衡化,会产生奇异的点,图像不和谐。一般采用的是用HSL和HSV色彩空间进行亮度的均衡。

对比度变换

这里首先介绍下对比度的概念。维基百科上说并没有对比度的严格概念。所以以下均为自己的一些理解。不同于上面HSV或者HSL模型,其中每一个概念均是针对像素值而言的。而对比度是针对图像整体而言的。

当图像为灰度图的时候,对比度定义为一副图像中最高和最低灰度级间的灰度差。当一副图像中最高和最低灰度级间的灰度度差为对比度。同样在灰度图中,像素值表示亮度值$L$或者说是明度值$V$或者说是灰度值,没有色调$H$的概念,饱和度$S=0$。所以对比度在数值上也就等于最高和最低亮度值、明度值、像素值的差。

低对比图有图像具有较窄的直方图,高对比图有图像具有较宽的直方图。

当图像为彩色图的时候,现在我所有看到的基本所有教程对对比度的变换公式均如下:

  • 参数$f(x)$为原图像像素
  • 参数$g(x)$表示输出图像像素
  • 参数$\alpha$称为增益($\alpha > 0$),常常被用来控制图像的对比度
  • 参数$\beta$称为偏置。常常被用来控制图像的亮度。

对于彩色图像,需要对每个通道的像素值均乘以$\alpha$。所以是不是可以理解为彩色图像的对比度为各个通道的最大像素值与最小像素值之间的差值。但是这样变换不会改变他们的色调$H$以及饱和度$S$么?这是一个问题。

思考

不管是对于显示器,还是对于JPG存储格式,均存储的是RGB颜色空间。在深度学习当中,理所当然的选取了直接RGB数据训练模型。而且实验结果也表明,RGB空间的结果较好。关于这部分的内容,可以看这篇论文ColorNet: Investigating the importance of color spaces for image classification以及这个回答RGB vs HSV colorspace

对于彩色图,当使用OpenCV等算法改变图像亮度$L$特征时,个人觉得不能改变图像的$H$和$S$,因此应该不能毫无规则的对R、G、B通道的像素值操作。若是将RGB转为HSL,直接改变$L$然后转为RGB,这样原理固然简单,但是复杂度很高。如何设计一种算法在不需要改变$H$和$S$的同时,改变$L$,应该是数字图像处理要重点研究的一个内容。一个比较简单的算法我们在从RGB到HSL的转换小节的例子中有介绍。

对于灰度图,就不用考虑这么多了,因为不管是HSV模型,还是HSL模型,$H$均无定义,$S$均为0,要想调节$L$或者$V$直接对像素值进行调节就行了。所以对于灰度图,不管是什么变换,在数值上均是改变的像素值,物理上变换的是亮度或者说是明度或者说是灰度值。值得注意的是,灰度图是没有饱和度的,不能对其进行饱和度变换。

虽然理论上灰度图的$H$是没有定义的,但是无论是看公式还是OpenCV转换结果,$H=0$。

png或者jpg图片不一定存在三个通道的值,也有可能是只保存了一个通道的灰度图。

疑惑

  • 彩色图像的对比度定义以及亮度等变换

参考

三原色光模式
File:Hsl-hsv models.svg
HSL和HSV色彩空间
RGB、HSV和HSL颜色空间
python opencv查看图片中任一点的像素 BGR值 灰度值 HSV值
色彩空间基础
深入理解color model(颜色模型)
Python图像处理库PIL的基本概念介绍
OpenCV+Python教程之2- 一个加载灰度图的入门程序
怎样用 rgb 三元组理解色相、亮度和饱和度? - 王赟 Maigo的回答 - 知乎
Contrast_ratio
直方图均衡化

------ 本文结束------
坚持原创技术分享,您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道