|
前言
读书期间一直接触图像处理的相关知识,工作之后成为了算法工程师,写这个系列主要是给初学者介绍一些入门级的算法,并给出代码,方便初学者有一个直观的认识,希望能更好的帮助初学者入门。由于本人理念是入门需要理论基础+实战体验,本人尽可能提供相关代码,给出demo。这一篇首先介绍一些入门的基础概念。
图像数字表示
复杂的图像获取和生成这里就不过多介绍了,一幅图像在计算机里面经过采样和量化之后是一个矩阵来表达,一般采样到0~255。一般常见的彩色图像为RGB 3通道图像,即三个矩阵,每个矩阵代分别代表红色、绿色、和蓝色通道。除了RBG彩色图像之外,常见的还有灰度图像和HSV颜色空间的图像。
灰度图像是利用一个颜色通道来表达图像,其计算方法为:
Gray = R*0.299 + G*0.587 + B*0.114 \\
灰度图像失去了颜色信息,对于图像处理来说,一般情况下处理彩色图像效果会略好于灰度图像(比如分类任务),灰度图一般是存储、计算量要求相对小一些。比如深度学习动辄上千万甚至上亿的图像数据,灰度图存储(包括拷贝)会方便一些。
HSV 表达彩色图像的方式由三个部分组成:
- Hue(色调、色相)
- Saturation(饱和度、色彩纯净度)
- Value(明度)
这种表达方式可能直观上不好理解,但对于计算机来而言能直观地表达颜色的色调、鲜艳程度和明暗程度,方便进行颜色的对比。HSV 空间 和 RGB 空间也是有对应的转换公式相对复杂一些:
示例:
####读取、显示、转换颜色空间
import sys, os
import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.figure(figsize=(14,14),dpi=100)
#读取图片
img = cv2.imread("Lena.png")
img_rgb = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
print("img_rgb shape", img_rgb.shape)
plt.subplot(1,3,1)
plt.imshow(img_rgb)
plt.title('RGB img')
#转灰度图
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
print("img_gray shape", img_gray.shape)
plt.subplot(1,3,2)
plt.imshow(img_gray, cmap = plt.cm.gray)
plt.title('grey img')
#转HSV
img_hsv = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2HSV)
print("img_hsv shape", img_hsv.shape)
plt.subplot(1,3,3)
plt.imshow(img_hsv)
plt.title('hsv img')
还有一些其他空间这里暂时不多介绍。不同的任务在RGB和HSV下会有不同的特点,最早在做比如图像增强以及肤色识别的时候,有时候会选择HSV空间来分析问题。此外,在深度学习算法中,有时为了追求效果采用模型融合,将不同颜色空间的图像作为输入也是产生模型gap的一种方式。
数字图像处理中常见概念
下面介绍下常见的一些简单的概念和操作。
图像邻域
以灰度图为例,图像是二维矩阵,图像中任意一点 f(i, j) ,以此点为中心,窗口大小为 k*k 的区域是该像素点的成为该像素点的邻域。常见的一些滤波,包括深度学习中的CNN的卷积,都是利用邻域来进行操作。
图像噪声模型
图像噪声机理这里不做过多介绍,只能说图像在产生的时候都会产生噪声,比如手机拍照的时候图像也是有噪声的,只不过手机会内置降噪的算法,而且还是多帧降噪的方法来进行降噪,这样呈现出来的才是无噪声(低噪声)图像。这里介绍一下常见的高斯噪声和 椒盐噪声。
高斯噪声
高斯噪声,顾名思义是指服从高斯分布(正态分布)的一类噪声,通常是因为不良照明和高温引起的传感器噪声。
椒盐噪声
椒盐噪声,通常是由图像传感器,传输信道,解压处理等产生的黑白相间的亮暗点噪声(椒-黑,盐-白)。
示例
####添加高斯噪声
ori_img = img_gray.copy()
plt.figure(figsize=(14,14),dpi=100)
def gaussian_noise(img, mean, sigma):
'''
此函数用将产生的高斯噪声加到图片上
传入:
img : 原图
mean : 均值
sigma : 标准差
返回:
gaussian_out : 噪声处理后的图片
noise : 对应的噪声
'''
# 将图片灰度标准化
img = img / 255
# 产生高斯 noise
noise = np.random.normal(mean, sigma, img.shape)
# 将噪声和图片叠加
gaussian_out = img + noise
# 将超过 1 的置 1,低于 0 的置 0
gaussian_out = np.clip(gaussian_out, 0, 1)
# 将图片灰度范围的恢复为 0-255
gaussian_out = np.uint8(gaussian_out*255)
# 将噪声范围搞为 0-255
# noise = np.uint8(noise*255)
return gaussian_out, noise
image_noise, sp_noise_plate = gaussian_noise(ori_img, 0, 0.1)
plt.subplot(1,3,1)
plt.imshow(ori_img, cmap = plt.cm.gray)
plt.title('ori img')
plt.subplot(1,3,2)
plt.imshow(image_noise, cmap = plt.cm.gray)
plt.title('add gaussian noise img')
plt.subplot(1,3,3)
plt.imshow(sp_noise_plate, cmap = plt.cm.gray)
plt.title('gaussian noise img')
####添加 椒盐噪声
ori_img = img_gray.copy()
plt.figure(figsize=(14,14),dpi=100)
def saltpepper_noise(image, proportion):
'''
此函数用于给图片添加椒盐噪声
image : 原始图片
proportion : 噪声比例
'''
image_copy = image.copy()
# 求得其高宽
img_Y, img_X = image.shape
# 噪声点的 X 坐标
X = np.random.randint(img_X,size=(int(proportion*img_X*img_Y),))
# 噪声点的 Y 坐标
Y = np.random.randint(img_Y,size=(int(proportion*img_X*img_Y),))
# 噪声点的坐标赋值
image_copy[Y, X] = np.random.choice([0, 255], size=(int(proportion*img_X*img_Y),))
# 噪声容器
sp_noise_plate = np.ones_like(image_copy) * 127
# 将噪声给噪声容器
sp_noise_plate[Y, X] = image_copy[Y, X]
return image_copy, sp_noise_plate
image_noise, sp_noise_plate = saltpepper_noise(ori_img, 0.1)
plt.subplot(1,3,1)
plt.imshow(ori_img, cmap = plt.cm.gray)
plt.title('ori img')
plt.subplot(1,3,2)
plt.imshow(image_noise, cmap = plt.cm.gray)
plt.title('add saltpepper noise img')
plt.subplot(1,3,3)
plt.imshow(sp_noise_plate, cmap = plt.cm.gray)
plt.title('saltpepper noise img')
图像模糊
图像模糊是图像处理中最简单和常用的操作之一,其主要目的之一是给图像预处理的时候降低图像噪声。比如,在大目标提取之前去除图像中的一些琐碎细节。图像模糊又被称为平滑滤波,这里介绍几个常见的滤波器。
均值滤波
均值滤波是最简单的滤波器,输出像素值是对应核窗口内像素的均值。
高斯滤波
高斯滤波器其对应窗口为归一化后的二维高斯分布矩阵,高斯滤波器对高斯分布的噪声效果比较好。
中值滤波
中值滤波原理比较简单,其是将图像中的每个像素用领域像素中的中值来代替,中值滤波对椒盐噪声有很好的抑制作用。
这里多说一些,这里常见的滤波器是手工设定的,而深度学习中的卷积核也是类似的滤波器,只不过里面的每个参数都是根据训练数据学习得到,没有数学上的表达式。深度学习之前,个人感觉基于non-local 的图像降噪算法,特别是BM3D算法,效果已经很好了,不过后面又出现了利用CNN 来做图像降噪再次刷新了指标,这些算法后面都会有专题介绍。
示例
## 高斯噪声降噪
ori_img = img_gray.copy()
plt.figure(figsize=(14,14),dpi=100)
image_gauss_noise, _ = gaussian_noise(ori_img, 0, 0.1)
# 均值滤波
img_mean = cv2.blur(image_gauss_noise, (5,5))
# 高斯滤波
img_Guassian = cv2.GaussianBlur(image_gauss_noise,(3,3),0, 0.2)
# 中值滤波
img_median = cv2.medianBlur(image_gauss_noise, 5)
plt.subplot(1,2,1)
plt.imshow(ori_img, cmap = plt.cm.gray)
plt.title('ori img')
plt.subplot(1,2,2)
plt.imshow(image_gauss_noise, cmap = plt.cm.gray)
plt.title('add gaussian noise img')
plt.figure(figsize=(14,14),dpi=100)
plt.subplot(1,3,1)
plt.imshow(img_mean, cmap = plt.cm.gray)
plt.title('img_mean')
plt.subplot(1,3,2)
plt.imshow(img_Guassian, cmap = plt.cm.gray)
plt.title('img_Guassian img')
plt.subplot(1,3,3)
plt.imshow(img_median, cmap = plt.cm.gray)
plt.title('img_median img')
## 椒盐噪声降噪
ori_img = img_gray.copy()
plt.figure(figsize=(14,14),dpi=100)
image_saltpepper_noise, _ = saltpepper_noise(ori_img, 0.2)
# 均值滤波
img_mean = cv2.blur(image_saltpepper_noise, (5,5))
# 高斯滤波
img_Guassian = cv2.GaussianBlur(image_saltpepper_noise,(5,5),0)
# 中值滤波
img_median = cv2.medianBlur(image_saltpepper_noise, 5)
plt.subplot(1,2,1)
plt.imshow(ori_img, cmap = plt.cm.gray)
plt.title('ori img')
plt.subplot(1,2,2)
plt.imshow(image_saltpepper_noise, cmap = plt.cm.gray)
plt.title('add saltpepper noise img')
plt.figure(figsize=(14,14),dpi=100)
plt.subplot(1,3,1)
plt.imshow(img_mean, cmap = plt.cm.gray)
plt.title('img_mean')
plt.subplot(1,3,2)
plt.imshow(img_Guassian, cmap = plt.cm.gray)
plt.title('img_Guassian img')
plt.subplot(1,3,3)
plt.imshow(img_median, cmap = plt.cm.gray)
plt.title('img_median img')
形态学操作
简单的图像处理还包括形态学操作,图像形态学操作是基于形状的一系列图像处理操作的合集,主要是基于集合论基础上的形态学数学。主要有四个操作:膨胀,腐蚀,开,闭。
形态学操作——膨胀
膨胀与腐蚀跟卷积操作类似,假设有图像A和结构元素B,B在A上面移动,其中B定义其中心是锚点,计算B覆盖下的A的最大像素值用来替代锚点的像素,其中B作为结构元素可以是任意形状。用公式来表达为:
AB={z|(B)zA} \\
形态学操作——腐蚀
腐蚀过程和膨胀过程类似,唯一不同的是以最小像素值替代锚点像素。
形态学操作——开操作
先腐蚀后膨胀,可以去掉小的对象
形态学操作——闭操作
先膨胀后腐蚀,可以填充小对象
这里仅介绍最简单的操作,还有一些相对复杂的形态学操作如顶帽、黑帽,有兴趣可以自行了解,不同的形态学操作的排列组合有时候能得到意想不到的结果,因而形态学操作往往作为复杂算法的中间润滑模块。
示例
#####形态学运算
element = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
print("elemrnt ", element )
import numpy as np
NpKernel = np.uint8(np.zeros((5,5)))
for i in range(5):
NpKernel[2, i] = 1
NpKernel[i, 2] = 1
print("NpKernel ", NpKernel )
""#34;
腐蚀
cv2.erode(src, # 输入图像
kernel, # 卷积核
dst=None,
anchor=None,
iterations=None, # 迭代次数,默认1
borderType=None,
borderValue=None)
膨胀
cv2.dilate(src, # 输入图像
kernel, # 卷积核
dst=None,
anchor=None,
iterations=None, # 迭代次数,默认1
borderType=None,
borderValue=None)
"""
import cv2
import numpy as np
original_img = cv2.imread('flower.png')
res = cv2.resize(original_img,None,fx=0.6, fy=0.6,
interpolation = cv2.INTER_CUBIC) #图形太大了缩小一点
B, G, R = cv2.split(res) #获取红色通道
img = R
_,RedThresh = cv2.threshold(img,160,255,cv2.THRESH_BINARY)
#OpenCV定义的结构矩形元素
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
eroded = cv2.erode(RedThresh,kernel) #腐蚀图像
dilated = cv2.dilate(RedThresh,kernel) #膨胀图像
plt.figure(figsize=(14,14),dpi=50)
plt.subplot(1,1,1)
plt.imshow(img, cmap = plt.cm.gray)
plt.title('R_channel_img')
plt.figure(figsize=(14,14),dpi=100)
plt.subplot(1,3,1)
plt.imshow(RedThresh, cmap = plt.cm.gray)
plt.title('RedThresh')
plt.subplot(1,3,2)
plt.imshow(eroded, cmap = plt.cm.gray)
plt.title('Eroded Image')
plt.subplot(1,3,3)
plt.imshow(dilated, cmap = plt.cm.gray)
plt.title('Dilated Image')
# cv2.imshow("original_img", res) #原图像
# cv2.imshow("R_channel_img", img) #红色通道图
# cv2.imshow("RedThresh", RedThresh) #红色阈值图像
# cv2.imshow("Eroded Image",eroded) #显示腐蚀后的图像
# cv2.imshow("Dilated Image",dilated) #显示膨胀后的图像
###开闭运算
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3)) #定义矩形结构元素
closed1 = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel,iterations=1) #闭运算1
closed2 = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel,iterations=5) #闭运算2
opened1 = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel,iterations=1) #开运算1
opened2 = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel,iterations=5) #开运算2
plt.figure(figsize=(14,14),dpi=100)
plt.subplot(1,3,1)
plt.imshow(img, cmap = plt.cm.gray)
plt.title('original')
plt.subplot(1,3,2)
plt.imshow(closed1, cmap = plt.cm.gray)
plt.title('Close1')
plt.subplot(1,3,3)
plt.imshow(closed2, cmap = plt.cm.gray)
plt.title('closed2')
plt.figure(figsize=(14,14),dpi=100)
plt.subplot(1,3,1)
plt.imshow(img, cmap = plt.cm.gray)
plt.title('original')
plt.subplot(1,3,2)
plt.imshow(opened1, cmap = plt.cm.gray)
plt.title('opened1')
plt.subplot(1,3,3)
plt.imshow(opened2, cmap = plt.cm.gray)
plt.title('opened2')
图像增强
由于受到环境,光线等的影响,拍摄的照片清晰度和对比度比较低,不能够突出图像中的重点。图像增强就是通过一定手段来增强图像的对比度,使得其中的人物或者事物更加明显,有利于后边的识别等处理。这里介绍下几种简单的图像增强方法。
直方图均衡
图像直方图是反映一个图像像素分布的统计表,其实横坐标代表了图像像素的种类,可以是灰度的,也可以是彩色的。纵坐标代表了每一种颜色值在图像中的像素总数或者占所有像素个数的百分比。图像是由像素构成,因为反映像素分布的直方图往往可以作为图像一个很重要的特征。直方图均衡是直方图最简单直接的应用。
均衡化的基本思想是:尽量使得每个灰度级的像素数量相等。直方图均衡化就是把一个已知灰度概率密度分布的图像经过一种变换,使之演变为一幅具有均匀灰度概率密度分布的新图像。算法具体方法为:
1)计算图像灰度直方图,并进行归一化(除以总像素数)
2)计算归一化直方图的累积直方图 fHist[ L-1 ]
3)对原始图灰度S,变换后的灰度D = fHist[S] * (L-1)
灰度变换
除去直方图均衡之外,还有一些其他的灰度变换,比如对数变换,伽马变换,灰度拉伸等等。
对数变换顾名思义就是对图像像素逐点进行对数变换,换主要用于将图像的低灰度值部分扩展,将其高灰度值部分压缩,以达到强调图像低灰度部分的目的,其计算公式为:
S=c log(1+r) \\
伽马变换是像素进行另外一种变换方式,其计算公式为:
S = cr^{\gamma} \\
灰度拉伸也用于强调图像的某个部分,与伽马变换与对数变换不同的是,灰度拉升可以改善图像的动态范围。可以将原来低对比度的图像拉伸为高对比度图像。实现灰度拉升的方法很多,其中最简单的一种就是线性拉伸,其计算公式为:
s =f(r)=ar+b \\
这里面每种变换都有相对复杂的形势,也可以尝试在不同的像素值范围采用不同的变换方式,即分段组合不同变换,同时一些常数还可以根据图像进行自适应估计,这样效果往往会更好。
示例
###图像增强--直方图
img = cv2.imread("Lena.png")
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hist = cv2.calcHist([img_gray],[0],None,[256],[0,256])
plt.figure()
plt.title("Grayscale Histogram")
plt.xlabel("Bins")
plt.ylabel("# of Pixels")
plt.plot(hist)
plt.xlim([0,256])
plt.show()
plt.figure(figsize=(14,14),dpi=100)
plt.subplot(1,2,1)
plt.imshow(img_gray, cmap = plt.cm.gray)
plt.title('original')
plt.subplot(1,2,2)
dst = cv2.equalizeHist(img_gray)
plt.imshow(dst, cmap = plt.cm.gray)
plt.title('equalizeHist')
####图像增强--对数变换
#对数变换
def log(c, img):
output = c * np.log(1.0 + img)
output = np.uint8(output + 0.5)
return output
img = cv2.imread("Lena.png")
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
output = log(15, img_gray)
plt.figure(figsize=(14,14),dpi=100)
plt.subplot(1,2,1)
plt.imshow(img_gray, cmap = plt.cm.gray)
plt.title('original')
plt.subplot(1,2,2)
dst = cv2.equalizeHist(img_gray)
plt.imshow(output, cmap = plt.cm.gray)
plt.title('log result')
####图像增强--伽马变换
def gamma(img, c, v):
lut = np.zeros(256, dtype=np.float32)
for i in range(256):
lut = c * i ** v
output_img = cv2.LUT(img, lut) #像素灰度值的映射
output_img = np.uint8(output_img+0.5)
return output_img
img = cv2.imread("Lena.png")
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
output = gamma(img_gray, 0.000000005, 4.0)
plt.figure(figsize=(14,14),dpi=100)
plt.subplot(1,2,1)
plt.imshow(img_gray, cmap = plt.cm.gray)
plt.title('original')
plt.subplot(1,2,2)
dst = cv2.equalizeHist(img_gray)
plt.imshow(output, cmap = plt.cm.gray)
plt.title('gamma result')
图像增强也是图像处理中一个基础研究领域,每年都会有论文,之前有一篇传统算法LIME图像增强效果很不错,最近也有效果拔群的水下图像增强算法Sea-thru,这里仅仅介绍入门的知识与概念,后面会有专题介绍更为复杂但是效果更好的算法。
尾声
就介绍到这里,主要介绍了图像处理领域一些最常见的概念,简单的一些算法和实例,其实在工作中这些入门级的算法也经常被用到,比如不同颜色空间的图像做输入,在深度学习中数据增强的时候会对图像加入噪声,一些形态学操作有时候也会用于分割算法的中间处理环节。目前这些算法都有开源库可以直接调用,记得自己刚开始入门的时候为了学习还自己实现一下,大家有兴趣也可以尝试。
这里多说一下,这里面很多的demo效果可能看起来不是特别的好,这是因为好多参数没有很好的设计,并且真正实用的话往往是多个不同简单操作的排列组合往往能达到不错的结果,这就需要经验和实践了,这里仅仅是入门介绍。
后续会不定期更新图像特征、降噪、增强、分类、检测、分割等系列算法文章。
参考文献
胡振宏:05-HSV和RGB的相互转换及色相反转
形态学操作
形态学-腐蚀、膨胀、开操作、闭操作_Fishmemory的博客-CSDN博客
图像直方图及直方图均衡总结(一)经典方法(附matlab和opencv端算法实现)_满城风絮-CSDN博客_图像直方图matlab代码
https://blog.csdn.net/piaoxuezhong/article/details/78269439
[数字图像处理]灰度变换--反转,对数变换,伽马变换,灰度拉伸,灰度切割,位图切割_zhoufan的专栏-CSDN博客_灰度变换对数变换 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|