内容简介
OpenCV是应用广泛的开源图像处理库,包括基本的图像处理方法:几何变换,形态学变换,图像平滑,直方图操作,模板匹配,霍夫变换;特征提取和描述方法:理解角点特征,Haeeis和Shi-Tomes算法,SIFT.SURF算法,Fast算法,ORB算法;以及openCV在视频操作中的应用,最后实践案例为人脸检测。
图像起源
图像是人类视觉的基础,是自然景物的客观反映。“图”是物体反射或透射光的分布,“像”是人的视觉系统所接受的图在人脑中所形成的印象或认识。
模拟图像和数字图像
模拟图像由某种物理量(光,电)的强弱变化来记录图像亮度信息,所以是连续变化的,易受干扰,已被数字图像替代。
数字图像:亮度由离散数值表示,是二维图像用有限数字数值像素的表示。分类有:二值图像,灰度图,彩色图。
二值图像
一维二值图像的二维矩阵仅由0,1两个值构成,“0”代表黑色,“1”代表白色。每个像素取值有0,1两种可能,所以计算机中二值图像的数据类型通常为一个二进制位。二值图通常用于文字,线条图和扫描识别(OCR)和掩膜图像的存储。
灰度图
。灰度数字图像是每个像素只有一个采样颜色的图像。. 这类图像通常显示为从最暗黑色到最亮的白色的灰度,尽管理论上这个采样可以任何颜色的不同深浅,甚至可以是不同亮度上的不同颜色。. 灰度图像与黑白图像不同,在计算机图像领域中黑白图像只有黑白两种颜色,灰度图像在黑色与白色之间还有许多级的颜色深度
索引图像
索引图像的文件结构比较复杂,除了存放图像的二维矩阵外,还包括一个称之为颜色索引矩阵MAP的二维数组。MAP的大小由存放图像的矩阵元素值域决定,如矩阵元素值域为[0,255],则MAP矩阵的大小为256Ⅹ3,用MAP=[RGB]表示。MAP中每一行的三个元素分别指定该行对应颜色的红、绿、蓝单色值,MAP中每一行对应图像矩阵像素的一个灰度值,如某一像素的灰度值为64,则该像素就与MAP中的第64行建立了映射关系,该像素在屏幕上的实际颜色由第64行的[RGB]组合决定。也就是说,图像在屏幕上显示时,每一像素的颜色由存放在矩阵中该像素的灰度值作为索引通过检索颜色索引矩阵MAP得到。索引图像的数据类型一般为8位无符号整型(unsigned int8),相应索引矩阵MAP的大小为256Ⅹ3,因此一般索引图像只能同时显示256种颜色,但通过改变索引矩阵,颜色的类型可以调整。索引图像的数据类型也可采用双精度浮点型(double)。索引图像一般用于存放色彩要求比较简单的图像,如Windows中色彩构成比较简单的壁纸多采用索引图像存放,如果图像的色彩比较复杂,就要用到RGB真彩色图像。
彩色图
每个像素通常由红,绿,蓝,三个分量来表示的。分量介于(0,255).RGB图像与索引图像一样都可用来表示彩色图像。与索引图象一样,由R,G,B三种颜色组合来表示每个像素的颜色。但与索引图像不同的是,RGB图像每一个像素的颜色值直接存放在颜色矩阵中,由于每一像素的颜色由RGB三个分量表示,M,N分别表示图像的行列数,三个M*N的二维矩阵分别表示每个像素的R,G,B三个颜色分量。RGB图像的数据类型一般为8位无符号整型,通常用于表示和存放真彩色图像。
OpenCV
计算机视觉开源软件库,支持与计算机视觉和机器学习的众多算法,并日益扩展。
优势
1:基于C++实现,提供python,ruby,matlab等语言接口。OpenCV-python是OpenCV的python API,结合OpenCV C++ API和python语言特性。
2:可以在不同系统平台上进行使用,包括Windows,Linux,OS X,Android和IOS.
3:丰富的API,完善的传统计算机视觉算法,涵盖主流的机器学习算法,同时添加了对深度学习的支持。
OpenCV-python
是一个python绑定库,旨在解决计算机视觉问题。
python语言简单易读,速度较慢。python使用c\c++扩展,在c/c++中编写计算密集型代码,并创建用作python的python包装器。好处是运行速度依旧很快,以及编写代码容易。
OpenCV-Python使用Numpy,这是一个高度优化的数据库操作库,具有MATLAB风格的语法。所有的OPENCV数组结构都转化为Numpy数组,与其他库集成更容易。
OPENCV模块
core :最核心的模块
highgui:视频与图像的读取,显示,存储
imgproc:图像处理的基础方法
features2d:图像特征以及特征匹配
OPENCV基本操作
读取图像:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
#读取图片
imge=cv.imread('E:\\code-python\\pythonProject1\\1.jpg',1)
显示图像:
cv.imshow("image",imge)
显示图像之后调用
cv.waitKey(0)
给图像绘制留下时间,否则窗口会出现无响应情况,图像也无法显示出来。
也可使用matplotlib来进行展示
plt.imshow(imge[:,:,::-1])
以灰度图形式进行保存
plt.imshow(imge,cmap=plt.cm.gray)
图像保存:
#图像保存
cv.imwrite("E:\\code-python\\pythonProject1\\1.png",imge)
绘制几何图形
绘制直线:
cv.line(img,start,end,color,thickness)
# 图像,起点,终点,颜色,线条宽度
绘制圆形:
cv.circle(img,centerpoint,r,color,thinkness)
# 图像,圆心,半径,颜色,线条宽度,为-1时生成闭合图案并填充颜色。
绘制矩形:
cv.rectangle(img,leftupper,rightdown,color,thinkness)
#图像,左上角,右下角,颜色,宽度
添加文字:
cv.putText(img,text,station,font,fontsize,color,thinkness,cv.LINE_AA)
#图像,文本数据,文本放置位置,字体,大小
效果显示:
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
#创建一个空白的图像
img = np.zeros((512,512,3),np.uint8)
#绘制图形
cv.line(img,(0,0),(511,511),(255,0,0),5)
cv.rectangle(img,(384,0),(510,128),(0,255,0),3)
cv.circle(img,(447,63),63,(0,0,255),-1)
font=cv.FONT_HERSHEY_SIMPLEX
cv.putText(img,'OpenCV',(10,500),font,4,(255,255,255),2,cv.LINE_AA)
#图像展示
plt.imshow(img[:,:,::-1])
plt.title('匹配结果'),plt.xticks([]),plt.yticks([])
plt.show()
获取像素点:
可以通过行和列的坐标获得该像素点的像素值。对于BGR图像,它返回一个蓝,绿,红值的数组。对于灰度图像,仅返回相应的强度值。使用相同的方法对像素进行修改。
import numpy as np
import cv2 as cv
img=cv.imread('E:\\code-python\\pythonProject1\\1.jpg')
#获取像素点的值
px=img[100,100]
#仅获取蓝色通道的强度值
blue=img[100,100,0]
#修改某个位置的像素值
img[100,100]=[255,255,255]
获取图像属性:
形状:img.shape
图像大小:img.size
数据类型:img.dtype
print(img.shape)
print(img.dtype)
print(img.size)
图像通道的拆分与合并:
b,g,r=cv.split(img)
img=cv.merge((b,g,r))
色彩空间的改变:
cv.cvtColor(input_image,flag)
gray=cv.cvtColor(dili,cv.COLOR_BGR2GRAY)
plt.imshow(gray,cmap=plt.cm.gray)
OPENCV算数操作
图像的加法:
可以使用OpenCV的cv.add函数把两个图像相加,或者可以简单的通过numpy操作添加两个图像,如res=img1+img2,两个图象应该具有相同的类型和大小,或者第二个图像为标量值。
注意:OpenCV加法和numpy加法之间存在差异。OpenCV的加法为饱和操作,而Numpy操作为模操作。
具体如下:
x=np.uint8([250])
y=np.uint8([10])
print(cv.add(x,y))#250+10=260=>255
print(x+y)#250+10=260%256=4
图像的混合:
按照不同的权重进行加法,会给人一种混合或者透明的感觉。公式如下:
g(x)=(1-a)f0(x)+af1(x)a取值为(0->1)
img1=cv.imread('E:\\code-python\\pythonProject1\\picture\\1.jpg',1)
img2=cv.imread('E:\\code-python\\pythonProject1\\picture\\2.jpg',1)
img3=cv.addWeighted(img1,0.7,img2,0.3,0)
cv.imshow("img3",img3)
cv.waitKey(0)
OPENCV图像处理
几何变换
图像缩放,平移
#获取行数列数
rows,cols=img1.shape[:2]
#绝对尺寸
res=cv.resize(img1,(2*cols,2*rows),interpolation=cv.INTER_CUBIC)
#相对尺寸
res1=cv.resize(img1,None,fx=0.5,fy=0.5)
plt.imshow(img1[:,:,::-1])
plt.show()
rows,cols=img1.shape[:2]
res=cv.resize(img1,(2*cols,2*rows))
plt.imshow(res[:,:,::-1])
plt.show()
图像平移;
cv.warpAffine(img,M,dsize)
图像,矩阵,大小
图像显示:
M为一个2*3的变换矩阵
M = [[x,y,z],[x1,y1,z1]]
M = np.float32([[1,0,100], [0,1,100]])
z为以左上角为坐标原点的x,z1为左上角为坐标原点的y,将图片进行平移
图片向x,y轴方向平移100距离
M=np.float32([[1,0,100],[0,1,100]])
res2=cv.warpAffine(img1,M,(cols,rows))
M为一个2*3的变换矩阵
M = [[x,y,z],[x1,y1,z1]]
M = np.float32([[1,0,0], [0,1,0]])
最初效果:
z为以左上角为坐标原点的x,z1为左上角为坐标原点的y,将图片进行平移
M = np.float32([[1,0,100], [0,1,100]])
图片效果:
图片向x,y轴方向平移100距离
当y为0,y1为1.5即图片以y轴方向拉长1.5倍,若y1为0.5即y轴方向图片压缩0.5
M = np.float32([[1,0,0], [0,1.5,0]]), y方向拉长1.5倍
,y方向缩放0.5倍
当x1为0,x为1.5即图片以x轴方向拉长1.5倍,若x为0.5即以x轴方向图片压缩0.5
M = np.float32([[1.5,0,0], [0,1,0]])
x轴方向拉长1.5倍
M = np.float32([[0.5,0,0], [0,1,0]]),x轴缩放0.5倍
当x,x1均不为0时,当x为1.2,x1为0.5时,即推向发生顺时针偏转,偏转角度tan& = x1/x 并且图像在x轴方向拉长1.5倍.M = np.float32([[1.2,0,0], [0.5,1,0]])
当y,y1均不为0时,当y为0.5,y1为1.2时,即图像发生逆时针偏转,偏转角为tan& = y/y1,并且图像在y轴方向拉长1.2倍
当x,x1,y,y1均不为0时,图片发生偏转。x,y均拉长1.2倍效果如下:
M = np.float32([[1.2,0.5,0], [0.5,1.2,0]])
图像旋转
图像旋转是指图像按照某个位置转动一定角度的过程,旋转中图像仍然保持着原始尺寸。图像旋转后图像的水平对称轴,垂直对称轴,及中心坐标原点都有可能发生变化,因此需要对图像旋转中的坐标进行相应转换。
cv.getRotationMatrix2D((cols/2,rows/2),90,1)
#进行旋转变换
dst=cv.warpAffine(img,M,(cols,rows))
#图像展示
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(img1[:,:,::-1])
axes[0].set_title("原图")
axes[1].imshow(dst[:,:,::-1])
axes[1].set_title("旋转后结果")
M=cv.getRotationMatrix2D((cols/2,rows/2),45,0.5)
res3=cv.warpAffine(img1,M,(cols,rows))
图像仿射变换
图像的仿射变换涉及到图像的形状位置角度的变化,是深度学习的常用功能,仿射变换主要是对图像的缩放。旋转,翻转和平移等操作的结合。
图像上的仿射变换, 其实就是图片中的一个像素点,通过某种变换,移动到另外一个地方。
从数学上来讲, 就是一个向量空间进行一次线形变换并加上平移向量, 从而变换到另外一个向量空间的过程。
对于二维坐标系的一个坐标点(x,y),可以使用一个2x2矩阵来调整x,y的值,而通过调整x,y可以实现二维形状的线性变换(旋转,缩放),所以整个转换过程就是对(x,y)调整的过程。
仿射变换(Affine Transformation)是指在向量空间中进行一次线性变换(乘以一个矩阵)和一次平移(加上一个向量),变换到另一个向量空间的过程。
仿射变换代表的是两幅图之间的映射关系,仿射变换矩阵为2x3的矩阵,如下图中的矩阵M,其中的B起着 平移 的作用,而A中的对角线决定 缩放,反对角线决定 旋转 或 错切。
所以仿射变换可以由一个矩阵A和一个向量B给出:
原像素点坐标(x,y),经过仿射变换后的点的坐标是T,则矩阵仿射变换基本算法原理:
所以仿射变换是一种二维坐标(x, y)到二维坐标(u, v)的线性变换,其数学表达式如下:
缩放和旋转通过矩阵乘法来实现,而平移是通过矩阵加法来实现的,为了将这几个操作都通过一个矩阵来实现,所以构造出了上面那个 2x3 的矩阵。但是这个会改变图像的尺寸,比如一个 2x2 的图像,乘以 2x3 的矩阵,会得到 2x3 的图像,所以为了解决这个问题,我们就增加一个维度,也就是构造齐次坐标矩阵。
最终得到的齐次坐标矩阵表示形式为:
仿射变换保持了二维图像的“平直性”和“平行性”:
平直性:
直线经仿射变换后还是直线
圆弧经仿射变换后还是圆弧
平行性:
直线之间的相对位置关系保持不变
平行线经仿射变换后依然为平行线
直线上点的位置顺序不会发生变化
向量间夹角可能会发生变化
#解决中文显示问题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
#创建变换矩阵
pst1=np.float32([[50,50],[200,50],[50,200]])
pst2=np.float32([[100,100],[200,50],[100,250]])
M=cv.getAffineTransform(pst1,pst2)
#完成仿射变换
dst=cv.warpAffine(img1,M,(cols,rows))
图像透射变换
透射变换是视角变化的结果,是指利用投射中点,像点,目标点三点共线的条件,按透视旋转定律使透视面(承影面)绕透视轴(迹线)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。
#透射变换#
pst1=np.float32([[56,65],[368,52],[28,387],[389,390]])
pst2=np.float32([[100,145],[300,100],[80,290],[310,300]])
T=cv.getPerspectiveTransform(pst1,pst2)
#完成透射变换
dst=cv.warpPerspective(img1,T,(cols,rows))
图像金字塔–多尺度表达
尺度,顾名思义就是说图像的尺寸和分辨率。在我们进行图像处理的时候,会经常对源图像的尺寸进行放大或者缩小的变换,进而转换为我们指定尺寸的目标图像。在对图像进行放大和缩小的变换的这个过程,我们称为尺度调整。
而图像金字塔则是图像多尺度调整表达的一种重要的方式,图像金字塔方法的原理是:将参加融合的的每幅图像分解为多尺度的金字塔图像序列,将低分辨率的图像在上层,高分辨率的图像在下层,上层图像的大小为前一层图像大小的1/4。图像金字塔用于机器视觉和图像压缩,一幅图像的金字塔型状排列分辨率依次降低,且来源于同一张原始图的图像集合。通过梯次向下采样获得,直到达到某个终止条件才停止采样。
图像的底部是待处理图像的高分辨率显示,而顶部是低分辨率近似,层级越高,图像越小,分辨率越低。
cv.pyrUp(img)上采样
cv.pyrDown(img)下采样
up_img=cv.pyrUp(img1)
img_1=cv.pyrDown(img1)
cv.imshow('enlarge',up_img)
cv.imshow('original',img1)
cv.imshow('shrink',img_1)
cv.waitKey(0)
cv.destroyAllWindows()
形态学变换
形态学转换是基于图像形状的一些简单操作。通常在二进制图像上执行。腐蚀和膨胀是两个最基本的形态学运算符。变体形式如开运算,闭运算,礼帽黑帽等。
连通性
在图像中,最小的单位是像素,每个像素周围有8个邻接像素,常见的邻接关系有三种:4邻接,8邻接,和D邻接。
连通性:4连通,8连通,m连通
4邻接:p在q的上下左右4个像素点内。
如图,灰色部分就是p的4邻域,那么灰色部分和p就是4邻接关系。
8邻接:p在q的周围的8个像素点内。
如图,灰色部分就是p的8邻域,灰色部分和p就是8邻接关系。
按照以上的定义,其实4邻接和8邻接是很好区分的,但是在实际问题处理上,这样的定义不够用了(二义性),大牛们就想办法解决问题。
如图,从 右上1 到 中间1 有2条通路,这种情况人们非常不愿意面对,就提出了m邻接来解决问题。
m邻接(混合邻接):(只要满足1个就是m邻接)
1. p和q是4邻接
2. q在p的ND中且p的N4相交q的N4为空,则q和p是m连接的
当像素间同时存在4邻接和8邻接时,优先采用4邻接。
腐蚀和膨胀
腐蚀和膨胀是最基本的形态学操作,腐蚀和膨胀都是针对白色部分(高亮部分)而言的。
膨胀就是使图像中高亮部分扩张,效果图拥有比原图更大的高亮区域,效果图拥有比原图更小的高亮区域。膨胀是求局部最大值的操作,腐蚀是求局部最小值的操作。
腐蚀
具体操作是用一个结构元素扫描图像中的每一个像素,用结构元素中的每一个像素做’与‘操作,若都为1,则该像素为1.否则为零。如图所示,A被B腐蚀后:
腐蚀的作用是消除物体边界点,使目标缩小,可以消除小于噪声元素的噪声点。
API:
cv.erode(img,kernel,iterations)
img:要处理的图像
kernel:核结构
iteration:处理次数。默认为1.
膨胀
具体操作是用一个结构元素扫描图像中的每一个像素,用结构元素中的每一个像素做’与‘操作,若都为0,则该像素为0.否则为1。如图所示,A被B膨胀后:
作用是将与物体接触的所有背景点合并到物体中,使目标增大,可填补目标中的孔洞。
API:
cv.dilate(img,kernel,iterations)
img:要处理的图像
kernel:核结构
iteration:处理次数。默认为1.
kernel=np.ones((5,5),np.uint8)
erosion=cv.erode(img1,kernel)
dilate=cv.dilate(img1,kernel)
fig,axes=plt.subplots(nrows=1,ncols=3,figsize=(10,8),dpi=100)
axes[0].imshow(img1)
axes[0].set_title('原图')
axes[1].imshow(erosion)
axes[1].set_title("腐蚀后结果")
axes[2].imshow(dilate)
axes[2].set_title("膨胀后结果")
开闭运算
开运算和闭运算是将腐蚀和膨胀按照一定的次序进行处理。但这两者并不是可逆的,即先开后闭并不能得到原来的图像。
开运算
开运算是先腐蚀后膨胀,其作用是:分离物体,消除小区域。特点:消除噪点,去除小的干扰块,而不影响原来的图像。
闭运算
闭运算和开运算相反,先膨胀后腐蚀,其作用是消除’闭合‘物体里面的孔洞,特点:可以填充闭合区域。
API:
cv.morphologyEx(img,op,kernel)
img:要处理的图像
op:开运算为cv.MORPH_OPEN,闭运算为cv.MORPH_CLOSE
kernel:核结构
kernel=np.ones((10,10),np.uint8)
cvOpen=cv.morphologyEx(img1,cv.MORPH_OPEN,kernel)#开运算
cvClose=cv.morphologyEx(img2,cv.MORPH_CLOSE,kernel)#闭运算
fig,axes=plt.subplots(nrows=2,ncols=2,figsize=(10,8))
axes[0,0].imshow(img1)
axes[0,0].set_title('原图')
axes[0,1].imshow(cvOpen)
axes[0,1].set_title("开运算结果")
axes[1,0].imshow(img2)
axes[1,0].set_title("原图")
axes[1,1].imshow(cvClose)
axes[1,1].set_title("闭运算结果")
礼帽和黑帽
礼帽运算
原图像与开运算的结果之差,如下所示:
dst=tophat(src,element)=src-open(src,element)
因为开运算的结果是放大了裂缝或者局部低亮度区域,因此,从原图中减去开运算后的图,得到的效果突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。礼帽操作用来分离比临近点亮一些的斑块。当一幅图像具有大幅背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。
黑帽运算
为闭运算的结果图与原图像之差。数学表达为:
dst=blackhat(src,element)=close(src,element)-src
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且之一操作和选择的核的大小相关。黑帽操作用来分离比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。黑帽操作用来分离比临近点暗一些的斑块。
cv.morphologyEx(img,op,kernel)
img:要处理的图像
op:处理方式
礼帽运算----cv.MORPH_TOPHAT
黑帽操作---- cv.MORPH_BLACKHAT
图像平滑
图像噪声
由于图像采集,处理,传输的过程中不可避免的受到噪声的污染,妨碍人们的图像理解及分析处理。常见的图像噪声有高斯噪声,椒盐噪声等。
椒盐噪声
椒盐噪声也成为脉冲噪声,是图像中经常见到的一种噪声,它是一种随机出现的白点或黑点,可能是亮的区域有黑色像素或是在暗的地方有白色色素(或者两者皆有)。椒盐噪声的成因可能是影像讯号收到突如其来的强烈干扰而产生,类比数位转换器或位元传输错误等。例如失效的感应器导致像素值为最小值,饱和的感应器导致像素值为最大值。
高斯噪声
噪声的概率密度分布是正态分布。
高斯噪声是指噪声密度函数服从高斯分布的一类噪声。由于高斯噪声在空间和频域中数学上的易处理性,这种噪声模型经常用于实践中。
高斯函数曲线为
图像平滑
图像平滑从信号处理的角度看就是去除其中的高频信息,保留低频信息。因此我们可以对图像实施低通滤波。低通滤波可以去除图像中的噪声,对图像进行平滑。
根据滤波器的不同可以分为均值滤波,高斯滤波,中值滤波,双边滤波。
均值滤波
采用均值滤波模板对图像噪声进行滤除,令中心在(x,y)点,尺寸为m×n的矩形子图像窗口的坐标组。均值滤波器可表示为:
由一个归一化卷积框完成的。它只是用卷积框覆盖区域所有像素的平均值来代替中心元素。例如:3×3标准化的平均滤波器如下所示:
均值滤波的优点是算法简单,计算速度快,缺点是在去噪的同时去除了很多细节部分,将图像变得模糊。
API:
cv.blur(src,ksize,anchor,borderType)
高斯滤波
二维高斯是构建高斯滤波器的基础,其概率分布函数如下所示:
高斯平滑的过程:
首先确定权重矩阵
假定中心点的坐标为(0,0),那么距离他最近的八个点的坐标如下:
为了计算权重矩阵,需要设定值为1.5,则模糊半径为1的权重矩阵如下:
这九个点的权重总和等于0.4787147,如果只计算这九个点的加权平均,还必须让他们权重之和等于1,因此上面9个值还要分别除以0.4787147,得到最终的权重矩阵。
计算高斯模糊:
有了权重矩阵,就可以计算高斯模糊的值了。
假设现有9个像素点,灰度图(0-255)如下:
每个点乘以权重值
将这9个值加起来,就是中心点的高斯模糊值。
对所有点重复这个过程,就得到了高斯模糊后的图像。如果原图是彩色图片,可以对RGB三个通道分别做高斯平滑。
API:
cv.GaussianBlur(src,ksize,sigmaX,sigmay,borderType)
blur=cv.GaussianBlur(imgx,(3,3),1)
plt.figure(figsize=(10,8),dpi=100)
plt.subplot(121),plt.imshow(imgx),plt.title('原图')
plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('高斯滤波后结果')
plt.xticks([]),plt.yticks([])
中值滤波
中值滤波是一种典型的非线性滤波技术,基本思想是用像素点邻域灰度值的中值来代替该点像素点的灰度值。中值滤波对椒盐噪声来说尤其有用,因为他不依赖邻域内那些与典型值差别很大的值。
API:
cv.medianBlur(src,ksize)
# 图像,卷积核大小
blur=cv.medianBlur(imgx,5)
直方图
直方图是对数据进行统计的一种方法,并且将统计值组织到一系列事先定义好的bin中,其中,bin为直方图中经常用到的一个概念,可以译为“直条”或“组距”,其数值是从数据中计算出的特征统计量,这些数据可以是诸如梯度,方向,色彩,或者其他特征。
图像直方图是用以表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像素个数。这种直方图中,横坐标的左侧为较暗区域,而右侧为较亮的区域。因此1张较暗的图片直方图数据多集中在左侧和中间部分,而整体明亮,只有少量阴影的图像则相反。
直方图是根据灰度图进行绘制的,而不是彩色图。一张图像的信息是(灰度值0-255,已知数子的范围包含256个值,于是可以按照一定规律将这个范围分割为子区域(也就是bins))
[0,255]=[0,15]∪[16,30]…[240,255]
然后再统计每一个bins的像素数目。可以得到下图(x轴表示bin,y轴表示每个bin中的像素个数)
dims:需要统计的特征数目。上例中,dims=1,因为仅仅统计了灰度值。
bins:每个特征空间子区段的数目,可译为“直条”或组距,bins=16.
range要统计特征的取值范围,上例中range=[0,255]
直方图的意义:图像中像素强度分布的图形表达方式。
统计了每一个像素值所具有的像素个数。
直方图的绘制
API:
cv.calcHist(img,channel,mask,hisssize,range[,hist[,accumulate]])
img:原图像
channel:输入的图像为灰度图,它的值为[0];彩色图像的话,传入的参数是[0],[1],[2]。它们分别是B,G,R。
mask:淹模图像
hisSize:BIN数目。
ranges:像素值范围[0,256].
imgx=cv.imread('C:\\Users\\DELL\\Desktop\\111\\1.png',0)
histr=cv.calcHist([imgx],[0],None,[256],[0,256])
plt.figure(figsize=(10,6),dpi=100)
plt.plot(histr)
plt.grid()
plt.show()
掩膜的应用
掩膜是指用选定的图像,图形,或物体,对要处理的图像进行遮挡,来控制图像处理的区域。
在数字图像中我们通常使用二维矩阵数组进行掩膜。掩膜是由0和1组合的一个二进制图像,利用该掩膜图像要处理的图像进行掩膜,1值被处理,0值屏蔽。
主要用途:提取感兴趣区域,用预先制作的感兴趣掩膜与待处理图像进行与操作,得到感兴趣图像,感兴趣区内图像值保持不变,而区外图像值都为0.
屏蔽作用:用掩膜对图像上的某些区域做屏蔽,使其不参与或不参加处理参数的计算,或仅对屏蔽区做处理或统计。
结构特征提取:用相似性变量或图像匹配方法检测和提取图像中与掩膜相似的结构特征。
特殊形状图像制作。
掩膜在遥感影像处理中使用较多,当提取道路或者河流,房屋时,通过一个掩膜矩阵对图像进行像素过滤。然后将我们需要的地物或者标志突出显示。我们使用cv.calcHist()来查找完整图像的直方图。要查找图像某些区域的直方图,在查找直方图的区域上创建一个白色的掩膜,否则黑色,将其作为掩码mask传递。
#直接以灰度图的形式读入
imgx=cv.imread('C:\\Users\\DELL\\Desktop\\111\\1.png',0)
#创建模板
mask=np.zeros(imgx.shape[:2],np.uint8)
mask[400:650,200:500]=255
mask_img=cv.bitwise_and(imgx,imgx,mask=mask)
#掩膜
mask_histr=cv.calcHist([imgx],[0],mask,[256],[1,256])
fig,axes=plt.subplots(nrows=2,ncols=2,figsize=(10,8))
axes[0,0].imshow(imgx,cmap=plt.cm.gray)
axes[0,0].set_title("原图")
axes[0,1].imshow(mask,cmap=plt.cm.gray)
axes[0,1].set_title("蒙版数据")
axes[1,0].imshow(mask_img,cmap=plt.cm.gray)
axes[1,0].set_title("掩膜后数据")
axes[1,1].plot(mask_histr)
axes[1,1].grid()
axes[1,1].imshow(imgx,cmap=plt.cm.gray)
axes[1,1].set_title("灰度直方图")
plt.show()
直方图均衡化
一幅图像大多数像素点都集中在某一个小的灰度值取值范围内,当图像整体明亮,那所有的像素值的取值都会很高,如图左。将该直方图做横向拉伸,就可以扩大图像像素值的分布范围,提高图像对比度。
“直方图均衡化”是把原始图像的灰度直方图从比较集中的某个灰度区间变成更广泛灰度范围内的分布。直方图均衡化就是对图像进行非线性拉伸,重新分配图像像素值,使一定灰度范围内的像素数量大致一致。
这种方法提高图像整体的对比度,特别是有用数据的像素值分布比较接近时,在X光图像中使用广泛,可以提高骨架结构的显示,另外在曝光过度或不足的图像中可以更好地突出细节。
API:
dst=cv.equalizeHist(img)
#均衡化处理
dst=cv.equalizeHist(img1)
#结果展示
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(img1)
axes[0].set_title('原图')
axes[1].imshow(dst)
axes[1].set_title("均衡化之后")
plt.show()
自适应的直方图均衡化
在直方图中,考虑的是全局对比度。在进行直方图均衡化之后,图片背景的对比度被改变了,丢失了很多信息,在许多情况下,这种效果不好。为减少信息损失,需要使用自适应直方图均衡化。整幅图被分解为很多小块,这些小块成为tiles(默认8×8),在对每一个小块进行直方图均衡化。在每一个区域中,直方图会集中在某一个小的区域中。如果有噪声的话,噪声会被放大,为避免这种情况出现,要采用对比度限制。对每个小块来说,如果直方图bin超过上限的话,就把其中的像素点均匀分布到其他bins,然后再进行直方图均衡化。
API:
cv.createCLAHE(clipLimit,tileGridSize)
clipLimit:对比度默认40
tileGridSize:分块大小,默认8×8
#自适应直方图均衡化
clahe=cv.createCLAHE(clipLimit=2.0,tileGridSize=(8,8))
cli=clahe.apply(img1)
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(img1,cmap=plt.cm.gray)
axes[0].set_title('原图')
axes[1].imshow(cli,cmap=plt.cm.gray)
axes[1].set_title("均衡化之后")
plt.show()
边缘检测
边缘检测是图像处理和计算机视觉中基本问题,边缘检测是表示数字图像中亮度变化明显的点。图像属性中的显著变化是反映了属性的重要事件和变化明显的点。图像属性的显著变化通常反映了属性的重要事件和变化。形式如下:
图像边缘检测大幅度减少了数据量,并且删除了可以认为不相关的信息,保留了图像重要的结构属性。边缘检测分为两类:基于搜索和基于零穿越。
基于搜索:寻找一阶导数中的最大值来检测边界,然后利用计算结果估计边缘的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值,代表算法有:sobel算子和Scharr算子。
基于零穿越:通过寻找图像的二阶导数零穿越来寻找边界,代表算法为Laplacian算子。
Sobel检测算子
边缘检测算法比较简单,实际应用比canny边缘检测效率高,但是边缘不如Canny检测的准确,在很多实际应用场合,sobel算子是高斯平滑与微分操作的结合体,对抗噪声能力很强,用途较多,尤其是效率较高,对纹理不太关心时。
方法:对于不连续的函数,一阶导数可以写作
假设要处理的图像为I,在两个方向求导
水平方向:将图像I与奇数大小的模版进行卷积,结果为Gx.比如当模板大小为3时,Gx为
垂直方向:将图像I与奇数大小的模版进行卷积,结果为Gy.比如当模板大小为3时,Gy为
在图像每一点,结合以上两个结果求出:
统计极大值所在的位置,就是图像的边缘。
当内核为3时,内核会产生明显的误差,为解决这一问题,我们使用Scharr函数,但该函数仅作用于大小为三的内核。该函数的运算与Sobel函数一样快,但结果更加准确,计算方法为:
API:Sobel_x_or_y=cv.Sobel(src,ddepth,dx,dy,dst,ksize,scale,delta,bor))
src:传入的图像
ddepth:图像的深度
dx,dy:求导的阶数,0表示没有求导,取值为0,1
ksize:算子大小即卷积核大小,须为奇数,1,3,5,7,9
scale缩放倒数的比例常数,默认没有伸缩系数
borderType:图像边界的模式,默认cv.BORDER_DEFAULT.
Sobel函数求导后会有负值,还有大于255的值。原图像是uint8,即8位无符号数,Sobel建立的图像位数不够,会有截断。要使用16位有符号数据类型,从v。CV_16S。处理完后,再使用cv.addweighted(src1,alpha,src2,beta)
#边缘检测
#计算卷积结果
x=cv.Sobel(img1,cv.CV_16S,1,0)
y=cv.Sobel(img1,cv.CV_16S,0,1)
#将数据进行转换
Scale_absX=cv.convertScaleAbs(x)#convert转换scale缩放
Scale_absY=cv.convertScaleAbs(y)
#计算结果
result=cv.addWeighted(Scale_absX,0.5,Scale_absY,0.5,0)
#图像显示
plt.figure(figsize=(10,8),dpi=100)
plt.subplot(121),plt.imshow(img1,cmap=plt.cm.gray),plt.title('原图')
plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(result,cmap=plt.cm.gray),plt.title('滤波后结果')
plt.xticks([]),plt.yticks([])
plt.show()
将Sobel算子ksize设为-1,就是利用Scharr进行边缘检测
x=cv.Sobel(img1,cv.CV_16S,1,0,ksize=-1)
y=cv.Sobel(img1,cv.CV_16S,0,1,ksize=-1)
Laplacian检测算子
Laplacian是利用二阶导数来检测边缘。因为图像是2维,我们需要在两个方向求导,如下图所示
API:
laplacian=cv.laplacian(src,ddepth[,dst[,ksize[,scale[,deltal[,borderType]]]]])
src:需要处理的图像
Depth:图像的深度,-1表示采用的是原图像相同的深度,目标图像的深度大于等于原图像的深度
ksize:算子大小
result=cv.Laplacian(img1,cv.CV_16S)
Scale_abs=cv.convertScaleAbs(result)
plt.figure(figsize=(10,8),dpi=100)
plt.subplot(121),plt.imshow(img1,cmap=plt.cm.gray),plt.title('原图')
plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(result,cmap=plt.cm.gray),plt.title('滤波后结果')
plt.xticks([]),plt.yticks([])
plt.show()
Canny检测算子
原理:
第一步:噪声去除
由于边缘检测很容易受到噪声的影响,所以首先使用5×5高斯滤波去除噪声。
第二步:计算图像梯度
对平滑后的图像使用Sobel算子计算水平方向和竖直方向的一阶导数(Gx和Gy)。根据得到的这两幅梯度图(Gx和Gy),找到边界的梯度和方向,公式如下:
第三步:非极大值抑制
在获得梯度的方向和大小后,对整幅图进行扫描,去除那些非边界线上的点。对每一个像素进行检查,看这个点的梯度是不是周围具有相同梯度方向的点中最大的。如下图:
A点位于图像的边缘,在梯度变化方向,选择像素点B和C,用来检测A点的梯度是否为极大值,若为极大值进行保留,否则A点被抑制,最后结果为具有细边的二进制图像。
第四步:滞后阈值
现在确定真正的边界,设置两个阈值:minval和maxval,当图像的灰度梯度高于maxval被认为是真正的边界点相连,如果是就认为它也是边界点,如果不是抛弃。如下图:
如上图所示,A高于maxval所以是真正的边界点,C虽然低于maxval但高于minval并且与A相连,所以也被认为是真正的边界点。B会被抛弃,因为低于maxval而且不与真正的边界点相连。所以选择合适的maxval,minval决定结果好坏。
API:
canny=cv.Canny(img,threshold1,threshold2)
img:灰度图
threshold1:minval,以较小的阈值将间断的边缘连接起来
threshold2:minval,以较小的阈值将间断的边缘连接起来
#边缘检测
lowThreshold=0
max_lowThreshold=100
canny=cv.Canny(img1,lowThreshold,max_lowThreshold)
#图像显示
plt.figure(figsize=(10,8),dpi=100)
plt.subplot(121),plt.imshow(img1,cmap=plt.cm.gray),plt.title('原图')
plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(canny,cmap=plt.cm.gray),plt.title('边缘检测结果')
plt.xticks([]),plt.yticks([])
plt.show()