前言
前一阵对图片处理感兴趣,于是就学习起来,却发现是个大坑:
而书里用的是 matlab,大学用完之后都没用过了。想在平时工作的 iOS 里用上,还需要学一下 OpenCV,而 OpenCV 还需要… 算了,我不想了。
等坑填好估计我已经老了吧 : )
目前先总结些图像处理的基础知识,记录下来,分享需要的小伙伴们…当然大佬们就不用看了。
index:
[TOC]
图像基础知识
图像格式
日常我们看到图片文件的后缀,就代表图片格式,常见的存储的格式有: jpeg,bmp,png,gif等等。图片格式是计算机存储图片的格式。
图片分类
图片可以分为位图
和矢量图
两类。
位图:也叫像素图,是使用像素点阵列拼合的图像。捕捉得到的图像如(拍摄、截图)都是像素图。像素图放大到一定程度会出现模糊。常见的像素图格式包括:JPEG、PSD、PNG、TIFF。
矢量图:是使用点线面构成的图像。矢量图往往是使用矢量软件绘制得到的。点线面都是数学化的,因此放大不会模糊。常见的矢量图格式包括:AI、EPS、SVG。
常见的几种位图格式
各种格式可以参见维基百科中的 图形文件格式比较 了解,对于 iOS 开发或者客户端来说,重点关注的位图格式,一般都是 .jpg / .png / .gif / .WebP 这 4 种,下面从 《移动端图片格式调研》 (非常推荐大家去阅读这篇文章)中摘录说明一下优缺点:
文件扩展名 | 诞生历史 | 优点 | 缺点 |
---|---|---|---|
PNG | 诞生在 1995 年,比 JPEG 晚几年。它本身的设计目的是替代 GIF 格式,所以它与 GIF 有更多相似的地方。 | 相对于 JPEG 和 GIF 来说,它最大的优势在于支持完整的透明通道。 | PNG 只支持无损压缩,所以它的压缩比是有上限的。 |
JPEG | 诞生于 1992 年,是一个很古老的格式 | 其压缩算法可以精确控制压缩比,以图像质量换得存储空间。由于它太过常见,以至于许多移动设备的 CPU 都支持针对它的硬编码与硬解码。 | 只支持有损压缩 |
GIF | 诞生于 1987 年,随着初代互联网流行开来 | 唯一的优势就是支持多帧动画,凭借这个特性,它得以从 Windows 1.0 时代流行至今,而且仍然大受欢迎。 | 它有很多缺点,比如通常情况下只支持 256 种颜色、透明通道只有 1 bit、文件压缩比不高。 |
WebP | 是 Google 在 2010 年发布的图片格式,希望以更高的压缩比替代 JPEG | 用 VP8 视频帧内编码作为其算法基础,取得了不错的压缩效果。它支持有损和无损压缩、支持完整的透明通道、也支持多帧动画,并且没有版权问题,是一种非常理想的图片格式。 | 对于复杂的图像(比如照片)来说,WebP 无损编码表现并不好 |
JPEG 和 PNG 对比
上面表格可以看到,JPEG 诞生的比 PNG 早,应用比较广,但 JPEG 是不支持 alpha 通道的。
这会产生什么问题呢?例如,iOS 压缩图片时,如果图片有地方是透明的,使用 JPEG 的方式,透明部分会得不到体现。使用 PNG 则可以完整支持。
我写了一个 demo 来实际验证这个问题,具体代码和效果可以到演示工程中查看。
首先用的一张原图是上半部分为白色,然后竖直向下,渐变到透明:
然后分别对 imageView 进行截图处理,然后分别采用 PNG 和 JPEG 两种方式来压缩生成新图片。
使用 PNG 格式压缩后得到的图片:
使用 JPEG 格式压缩后得到的图片:
通过对比,可以发现:
JPEG 处理( JPEG 的压缩比设置为 0.9 )后,损失了原图的透明部分,变成了白色。PNG 则可以很好的支持透明通道。
JPEG 的图片大小,这里是 81 KB ,PNG 的则达到了 487 KB ,对同一张图片,二者大小的差距可以达到 6 倍以上 .
所以 JPEG 和 PNG 的方式各有优劣,JPEG 在图片大小上有巨大优势,但是图片的呈现上 PNG 无疑更好,使用大家可以根据场景去调整。
压缩方式 - 有损压缩/无损压缩
上面说了 JPEG 和 PNG 的不同。那么我们再进一步探索,是什么造成了 JPEG 和 PNG 在呈现上和大小上的不同呢?
那这里就要提到压缩方式。
gif/png 使用的是无损压缩,而 jpeg 是有损压缩。WebP 则是两种都有支持(WebP 露出胜利的笑容✌️)。
下面来说一下两种压缩方式:
无损数据压缩(Lossless Compression),是指资料经过压缩后,信息不被破坏,还能完全恢复到压缩前的原样。无损压缩通常用在严格要求“经过压缩、解压缩的资料必须与原始资料一致”的场景。
无损压缩,举个例子,本来一句话是:
TimWang 说 TimZhang 是 TimWang 的兄弟。TimZhang 说 TimZhang 不是 TimWang 的兄弟。
现在进行无损压缩,先进行定义:
Tim=a,Zhang=1,Wang=2,兄弟=b
然后这句话变成:
a2 说 a1 是 a2 的 b。a1 说 a1 不是 a2 的 b.
那么很容易看到,数据的长度明显减少了(压缩),根据事先定义,能把信息给完全还原(无损),所以叫无损压缩。
有损数据压缩(Lossy Compression),经过此方法压缩、解压的数据会与原始数据不同但是非常接近。借由将次要的数据舍弃,牺牲一些质量来减少数据量、提高压缩比。
有损压缩,同样以一句话为例子:
TimWang 说 TimZhang 是 TimWang 的兄弟。TimZhang 说 TimZhang 不是 TimWang 的兄弟。
进行有损压缩,对我们的数据进行处理,仍然不影响它的表达:
TimWang 说是 TimZhang 兄弟。TimZhang 说不是 TimWang 兄弟。
可以看到,数据长度也被减少了(压缩),通过丢掉“不重要”信息(有损),留下“关键信息”,来达到我们的压缩目的,只是无法还原了,所以叫有损压缩。
经过上面说明,已经对无损/有损的压缩方式有大概的理解。这里不再赘述,传统武术讲究点到为止,关于两种压缩方式想了解更多的同学自行去入坑吧
位图的颜色模型
上面有介绍到,位图(Bitmap)
是通过像素点阵列来表示的图像。
位图的像素都分配有特定的位置和颜色值,每个像素的颜色信息由RGB 组合
或者灰度值
表示。
从位图图片中,选择最有代表性的若干种颜色(通常不超过256种)编制成颜色表,然后将图片中原有颜色,用颜色表的索引来表示。
而这提到的 RGB 组合
和 颜色表
,就需要了解 颜色模型。
什么是颜色模型
颜色模型 是计算机中表示各种颜色的模型,是色彩的一种描述方式。
一个颜色通常由多个变量来标示,相当于 空间坐标。
而其实一个颜色是客观的,固定的,只是不同的颜色模型是从不同的角度取描述它,就像 11 = 10 + 1,也可以是 11 = 2 + 9。
颜色模型决定一个颜色根据什么样的“规则”来进行表达。
色彩模型有很多,下面 6 种是常用的模型:
- RGB
- YUV
- HSV
- HSL
- CMY/CMYK
- LAB
又可分为两类:
- 面向硬件设备的,如彩色显示器,打印机等,RGB(彩色摄像机/彩色摄像机),CMYK/CMY(彩色打印机),YUV(电视系统)
- 面向彩色处理等,如为动画创建的彩图,HSL/HSV/LAB。
文章的篇幅有限,下面只对 RGB 和 HSV 2 种色彩模型进行简略说明。
RGB
RGB 又叫 三原色光模式(RGB color model),是一种加色模型 :
将红 (Red)/绿(Green)/蓝(Blue)三原色的色光以不同的比例相加,以合成产生各种色彩效果。
至于三原色为什么是红/绿/蓝,而不是其它的颜色组成,维基百科中对于 选择红绿蓝的生理原因 的解释是:
三原色的原理不是出于物理原因,而是由于生理原因造成的。
人的眼睛内有几种辨别颜色的锥形感光细胞,分别对黄绿色/绿色/蓝紫色的光最敏感:如果辨别黄绿色的细胞受到的刺激略大于辨别绿色的细胞,人的感觉是黄色;如果辨别黄绿色的细胞受到的刺激大大高于辨别绿色的细胞,人的感觉是红色。
虽然三种细胞并不是分别对红色/绿色/蓝色最敏感,但这三种光可以分别对三种锥形细胞产生刺激。
所以,我们可以认为 :
红光+绿光 产生的视觉效果等于黄光,而不是等于变成了黄光本身。
就像盐水和纯净水,都是透明的液体,视觉效果上是一样的,实际上并非同一事物。
RGB 颜色空间是一种均匀性较差的颜色空间,RGB 颜色空间适合于显示系统,却并不适合于图像处理。
iOS 上能直接使用增加了 Alpha 通道的 RGBA 模型表示颜色:
1 | UIColor *color = [UIColor colorWithRed:39/255.0 green:45/255.0 blue:51/255.0 alpha:0]; |
HSV
HSV 颜色模型在图像处理中有大量的应用,它由三个部分组成:
- H: Hue,色调值
- S: Saturation,饱和度值
- V: Value,亮度值
Hue 用角度表达,取值范围是 0-360°,表示色彩的信息。
Saturation 用百分比表达,取值范围 0~1。饱和度减小,就是往光谱色中添加白色,光谱色所占的比例也在减小。饱和度为0,表示光谱色所占的比例为0,导致整个颜色呈现白色。
Value 用百分比表达,取值范围 0~1。明度减小,就是往光谱色中添加黑色,光谱色所占的比例也在减小,明度为0,表示光谱色所占的比例为0,导致整个颜色呈现黑色。
HSV 与人的视觉特性比较接近,具有较强的感知度。HSV 目前广泛应用于计算机图形学,科学计算可视化等领域。与 RGB 不同的是,它是一个均匀颜色空间。HSV 因为更近于人对颜色的主观感受,很适合基于颜色的图像相似度比较。
iOS 中也可以直接使用 HSV 表示颜色:
1 | UIColor *hsvColor = [UIColor colorWithHue:0.10 saturation:0.79 brightness:0.53 alpha:1.00]; |
note:
各种色彩模型都是同一物理量的不同表达,因此有可以互相转化的关系。
例如 RGB/HSV ,都是可以通过公式,互相转化成对应坐标的。如 OpenCV 等也有提供封装好的函数去转换。
颜色模型和颜色空间的关系
前面说到颜色模型,大家也看到,模型都是建立在三维空间的,所以它和颜色空间也是密不可分的。
那颜色模型和颜色空间的关系到底是什么呢?
如果开发经历稍微丰富的同学,日常肯定会发现一件事:当我们使用某个颜色模型,同样的值 xxxx 表达一个颜色,却可能在不同的设备(例如模拟器/真机)上存在显示差异。
而造成差异的原因,就在于使用的颜色空间不同。在不同的颜色空间上,对于颜色的呈现是不一样的。
一个色彩模型下可以有不同的色彩空间,根据排列条件不同,有不同色域(所能表示色彩的范围)和含义。
色彩模型具体到一种色彩空间上才有实用性,以前面提到的 RGB 模型为例,就存在sRGB/AdobeRGB/AppleRGB 等颜色空间,它们大多也与图像外设是相关的。
AdobeRGB 与 sRGB 最为常见,下面演示一下两种色彩空间(演示资料来自网络):
从面积来看,AdobeRGB 是要比 sRGB 更大的,也就是说能表示的色彩范围更大。
平常设计稿当中的 RGB 颜色空间,大多使用的是sRGB,有时难免会造成颜色差异的小问题。
下次再碰到设备显示颜色不一致问题,就可以和设计师对一对所用的色彩空间。
位图存储
经过之前的一些了解,现在看看位图的存储。
图片使用 RGB 颜色模型,则每个像素都使用 RGB 表达,来组成像素矩阵:
在实际开发中, RGBA 的存储也很多,增加了 Aplha 通道,例如 PNG 格式图片。
一般来说,如果一个通道存储需要 8 位,那一个像素占的位数:RGB 是 3x8=24 位,RGBA 是 4x8=32 位。
虽然上面咱们看到的是一个矩阵,其实到了内存中,只能一维的存储:
按行排列,将像素从上往下或从下往上的放入。
值得注意的是,RGB 或者 RGBA 的存储顺序可能也是不同的,如图:
一般来说有 RGB、BGR、RGBA、BGRA 的排列,大部分是 BGR\BGRA 排列。
位图深度
上面说到了位图的存储,具体到 1 个像素所占的位数,就是位图深度
。它又叫色彩深度,或者说色彩位数。
色彩位数
表示在位图中储存每一像素所用的位数,常用单位为位 /像素(bpp–bits per pixel)。
要用多少个二进制位来表示每个点的颜色,是分辨率的一个重要指标:
色彩位数 越高,可用的颜色就越多 – 色彩深度是 n 位, 那就有 $2^n$ 种颜色来选择。
位深度为 1 的像素位图,只有2( $2^1$ )个可能的值(黑色和白色),所以又称为二值位图。
位深度为 8 的图像,有 256( $2^8$) 个可能的值(或256种灰度等级)。
而如果色彩位数达到了24位,则可称之为真彩色,它能组合成2的24次幂种颜色,即:16777216种颜色 ( 或称千万种颜色 ),超过了人眼能够分辨的颜色数量。
色彩位数常用有1位(单色),2位(4色,CGA),4位(16色,VGA),8位(256色),16位(增强色),24位和32位等。
位图大小计算
颜色就越多,颜色表现就越逼真,但是每 1 个像素占用的位数也多了,相应的数据量越大。
所以色彩位数也决定了图片的大小,计算公式为:图片大小(单位bit) = 长x宽x色彩位数
例如一个 100*100 的图片,色彩位数位 32,那大小就为:100X100X32 = 320000 bits-> 40000 bytes。
另外关于 高动态范围影像(High Dynamic Range Image),也就是 iPhone 手机上的 HDR 模式。
它最主要是分别使用三种图片(欠曝、正常曝光、过曝),最后合成一张图。无论是光位还是暗位的细节都得到体现。
HDR 使用超过一般的 32 位色阶来储存影像。通常来说每个像素会分配到
32+32+32
个 bit 来储存颜色信息,每个原色都使用一个 32 bit 来储存,也就是 说它能表现的颜色数量达到了 2 的 32 次幂。丰富的色彩,这也是支撑它表现细节的一个关键。
图片灰度
上面在说颜色空间时有提到:
位图像素分配有特定的位置和颜色值,每个像素的颜色信息由 RGB 组合或
灰度值
表示。
什么是灰度
灰度也可以认为是亮度,简单说就是色彩的深浅程度。
说到亮度,最亮的就是白色,最不亮的就是黑色
,黑白之间存在灰色地带,所以就叫做灰度。
RGB 位图的灰度
单通道图像只能显示灰度图,RGB 三通道图像也能显示灰度图。
只不过单通道灰度图每个像素值是8位,而三通道每个像素值是24位。上面说过了,同样宽高下,像素位数决定了存储大小,所以单通道图像比三通道图像的存储量要小。
下面进一步画图来表示 RGB 的灰度级,以R/G/B为轴建立坐标系,黑(0,0,0)为原点,不同的坐标代表不同的颜色。
其中,在(0,0,0)与(1,1,1)的对角线上的颜色是不同层次的灰度级
。
通过图片里的对角线,按三维坐标来计算 RGB 灰度的话,即是 R=G=B.
灰度级
图片的灰度级,是表明图像中不同灰度的最大数量。
灰度级越大,图像的亮度范围越大。
图片能够展现的灰度数量越多,意味着能实现更强的色彩层次,色彩更丰富,例如 RGB 位图为 16级灰度,能显示的颜色就是 16×16×16 = 4096 色。
一张图片的灰度级,和位图深度有关:
如果位图深度是 n 位,位图灰度级 = $2^n$ 。
目前来说,位图大都是 256级(RGB 单个通道用 8 位存储,2 的 8 次幂)灰度 。
灰度图
灰度图每个像素只有一个采样颜色,用从黑到白之间的灰度值来表示,例如灰度图的每个像素用8位,则像素点的灰度值是黑白间的256种灰度级中的一种 (0~255)。
而图像的灰度化,就是让像素点矩阵中的每一个像素点都满足关系:
R=G=B,此时的这个值叫做灰度值。
如 :RGB(100,100,100) 就代表灰度值为 100, RGB(50,50,50) 代表灰度值为 50。
灰度值越大表示越亮。灰度值最高的时候显示是白色,反之,像素越暗,灰度值越低,灰度值为 0 显示是黑色。
通过灰度值的不同,令像素呈现不同程度的灰色,展现了不同的灰度层次,灰度图也就展现出来了。
图片灰度化处理
在许多场合下,灰度图像是彩色图像的一种简化表示,反应彩色图像的明亮度。
图像灰度化处理可作为图像处理预处理步骤,为图像分割/图像识别/图像分析等上层操作做准备。
一般灰度化处理的方法有 4 种 :分量法/最大值法/平均值法/加权平均法。
下面选取一张杨超越的图片:
分别利用上述 4 种方式处理,来得到不同灰度图像,看一下效果。
加权平均法
将三个分量以不同的权值进行加权平均。
人眼对绿色的敏感最高,对蓝色敏感最低,因此,按下式对RGB三分量进行加权平均能得到较合理的灰度图像:
Gray = Gx0.59+Rx0.3+Bx0.11
平均值法
彩色图像中的三分量亮度求平均得到一个灰度值。
Gray =(R+G+B)/3
最大值法
彩色图像中的三分量亮度的最大值作为灰度图的灰度值。
Gray = Max(Max(R,G),B)
分量法
根据彩色图像中的三分量的亮度,作为三个灰度图像的灰度值,形成三个新图像。再选取一种需要的灰度图像
Gray = R
Gray = G
Gray = B
生成上面的几种图片代码,均以上传到 GitHub ,为里面的 [UIImage grayForImage:forType:]
方法。
图片二值化
在前面有提到说图像灰度化是图像分割的一个前置操作。做完灰度化后,常常要做的,就是进一步把图片二值化。
什么是图片二值化
二值化就是让图像的像素点矩阵中的每个像素点的灰度值为黑色和白色,让整个图像呈现只有黑和白的效果。
例如在灰度级为 256 时,在二值化后的图像中的灰度值,只有 0 或 255 。
图片二值化的作用
二值图像在数字图像处理占有非常重要的地位,图像的二值化相对于彩色图像来说,运算量小很多,图像中数据量大为减少。
而进行图片二值化操作,一般都是想从图像中提取出目标物体。
如何进行灰度图像二值化处理
一般来说,要进行二值化处理,先进行灰度化,再根据灰度值判断,进行黑白转化。
那么一个像素点在灰度化之后,它的灰度值怎么转化黑或者白?
比如灰度值为188,那二值化后是黑还是白?这就涉及到取一个阀值的问题。
这里有 3 种比较基本的方法:
1、中间值法
例如取阀值为127(0~255的中数),灰度值小于等于127的为黑色,大于127的变为白色。优势在于计算量小速度快,缺点是阀值在不同的图片中均为127。不同的图片,颜色分布差别很大,所以用127做阀值一刀切,效果肯定是不好的。
2、所有像素点的灰度值的平均值
先求取一个像素点平均值:
像素点平均值 = (像素点1灰度值+…+像素点n灰度值)/ n 。
然后将 像素点平均值 作为阀值。这样做比 中间值法 好一些。
3、直方图方法(也叫双峰法)。
直方图是图像的重要特质。直方图方法认为图像由前景和背景组成,在灰度直方图上,前景和背景都形成高峰,在双峰之间的最低谷处就是阀值所在。
要说明的是,这 3 种方式并非通用的万能方法,算是比较粗糙的处理方式。
二值化处理有很多针对不同情况的算法,感兴趣的建议自己深入了解。
灰度直方图
这里说明一下灰度直方图,它的横轴是灰度级,竖轴则是各灰度级的像素个数。
从灰度直方图,我们可以很直观的看到个灰度级像素的比较,灰度分布情况是图像的一个重要特征。
二值化处理演示
仍然取一个杨超越的图片来做演示:
二值化的阀值为了便于计算,直接按比例从 0~1 之中取一个。
先将彩色图片像素灰度化(加权平均),然后大于阀值为白色,小于阀值为黑色。
阀值 = 0.4
阀值 = 0.5
阀值 = 0.6
相关代码也已经上传到 ,均以上传到 GitHub ,为里面的 [UIImage covertToBinaryzation:]
方法。
二值化处理,最关键的地方就在于 取阀值。用什么样的值,在哪里的范围用,决定了二值化的效果。
这里只是非常简单的演示。
图片二值化的具体应用有很多,我目前想到在客户端,就有二维码扫描,提取图片签名/印章,转化商家彩色图标等等。
若有不正确理解的地方,欢迎大家指正 。