opencv笔记(2)图像滤波器

opencv笔记(2)图像滤波器

图像滤波器

滤波器在数字信号处理中其实挺常见的,尤其是声音处理里面,因为有时域和频域的转化,所以在频域上可以做出一些操作,比如用滤波器过滤掉某些频率的波,有高通滤波器和低通滤波器,分别过滤掉频率低的声波和频率高的声波。

在图像领域也有同样的操作,图像中的低通滤波器就是过滤掉变化过大的像素点,换句话说就是模糊化处理,而高通滤波器,同理的,是对于图像尖锐化处理。

我们可以称图像中的滤波器为卷积核,其实也是卷积神经网络中常见的一个组成成分。我们在下面写一个\(3\times3\)的卷积核。
\[ \begin{pmatrix} c_1 & c_2 & c_3 \\ c_4 & c_5 & c_6 \\ c_7 & c_8 & c_9\end{pmatrix} \]
上述的卷积核对图像进行卷积操作,然后输出新的图像。可以表述成如下的数学形式:
\[ f:Image\rarr Image'\\ \begin{pmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{pmatrix}\rarr \begin{pmatrix} a_{1,1}' & a_{1,2}' & \cdots & a_{1,n-2}' \\ a_{2,1}' & a_{2,2}' & \cdots & a_{2,n-2} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n-2,1}' & a_{n-2,2}' & \cdots & a_{n-2,n-2}' \\ \end{pmatrix} \\where\\ a_{1,1}'=c_1a_{1,1}+c_2a_{1,2}+c_3a_{1,3}+\cdots+c_9a_{3,3} \\\vdots\\ a_{n-2,n-2}'=c_1a_{n-2,n-2}+c_2a_{n-1,n-2}+c_3a_{n,n-2}+\cdots+c_9a_{n,n} \]
换句话说,就是用卷积核和图片中同卷积核大小一致的部分进行数量积操作,然后操作得到的数量积视为新的像素点。

图像的模糊处理

顾名思义,模糊处理就是降低图像像素点之间数值的跳跃性,换句话说就是尽可能的取范围内的平均值(在金融领域的移动平均也是这样一种处理,增加曲线光滑性)。我们可以用如下的卷积核做模糊化处理。
\[ \frac{1}{9}\begin{pmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1\end{pmatrix} \]
在opencv中我们做卷积处理时使用函数filter2D

filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) -> dst

src表示输入的图片(source),ddepth表示存储的数据类型,比如无符号字符CV_8U,无符号短字符CV_16U……当值为\(-1\)时与原图像保持一致。kernel表示卷积核,一般是一个\(n\times n\)的矩阵,dst表示与输入图片大小一致的输出图片,anchor表示内核的基准点,基准点就是卷积核中与进行处理的像素点位置一致的点,delta表示可选的偏置值,borderType表示边缘像素处理的方法,包括各种空像素值的填充等等。详细

官方文档上说这个函数实际上计算的是相关性,而非卷积。
\[ {dst} (x,y) = \sum _{{0\leq x' < {kernel.cols},}{0\leq y' < {kernel.rows}} } {kernel} (x',y')* {src} (x+x'- {anchor.x} ,y+y'- {anchor.y} ) \]
事实上相关系数也是卷积的一种,他们是这样发生关系的
\[ f(t)\circ g(t)=f*(-t)\circ g(t) \]
因此如果我们想计算真正的卷积,我们就需要将图片翻转,并且将新的anchor设置为

kernel.cols-anchor.x-1,kernel.rows-anchor.y-1

我们还用之前的图片做例子

kernel_3_3 = np.ones((3,3), np.float32)/9.0
output = cv2.filter2D(img, -1, kernel_3_3)

之后再用np.hstack生成两张图片的对比

output2=np.hstack((img,output))

如果说\(3\times 3\)模糊度的卷积核不够明显,我们看看\(7\times 7\)

扫描二维码关注公众号,回复: 9496203 查看本文章

除了filter2D函数外,还可以用blur函数,blur函数具有如下参数。

blur(src, ksize[, dst[, anchor[, borderType]]]) -> dst

其中src是输入图片,ksize是卷积核大小,其他参数都和filter2D一样。

output=cv2.blur(img,(3,3))

图片的边缘检测

边缘检测就是检测数字图像中亮度变化明显的点,有些边缘检测是基于亮度的一阶导数,有些则基于二阶导数,一阶倒数是原始亮度函数的梯度。我们可以这样计算此导数
\[ I'(x)=\frac{1}{2}(I(x+1)-I(x-1))=-\frac{1}{2}I(x-1)+0I(x)+\frac{1}{2}I(x+1) \]
换句话说我们在一维空间可以定义其卷积为\((-\frac{1}{2},1,\frac{1}{2})\)。同理的,我们可以构建其在二维空间的卷积
\[ \begin{pmatrix} \frac{1}{4} & -\frac{1}{2} & -\frac{1}{4}\\ -\frac{1}{2} & 1 & \frac{1}{2}\\-\frac{1}{4} & \frac{1}{2} & \frac{1}{4}\end{pmatrix} \]
因此我们可以用上述矩阵计算出二维图像的亮度梯度。

而基于二阶倒数,我们可以找到亮度梯度的变换率。如果图像中存在边线,则图像的亮度梯度变化率就非常大,而且边线两边梯度符号相反,因此我们可以找二阶导数过原点的点。我们可以如下计算二阶导数
\[ I''(x)=(I(x+1)-I(x))-(I(x)-I(x-1))=I(x-1)-2I(x)+I(x+1) \]
我们在一维空间定义卷积为\((1,-2,1)\),则在二维空间构建卷积
\[ \begin{pmatrix}1 & -2 & 1\\-2 & 4 & -2\\1 & -2 & 1\end{pmatrix} \]

通过计算出边缘导数,下一步就可以通过确定阈值来计算边缘位置,阈值越低检测出的边线越多,但相对应的,冗余信息也就越多,而高阈值会损失掉一部分或细或短的线。

边缘检测的算子也有很多,我们上述的一阶导和二阶导都是边缘检测算子,除此之外,一阶的边缘检测算子还包括Roberts Cross算子,Prewitt算子,Sobel算子,Canny算子和罗盘算子,二阶的边缘检测算子有Marr-Hildreth算子等等。

Sobel算子

我们先考虑Sobel算子,Sobel算子采用了一阶导的原理,因此只考虑横向或者纵向临近三个像素点值的变换速度。

Sobel算子是由下列两个矩阵组成的,一个是横向一个是纵向。
\[ S_x=\begin{pmatrix}-1 & 0 & 1\\-2 & 0 & 2\\-1 & 0 & 1\end{pmatrix} S_y=\begin{pmatrix}-1 & -2 & -1\\0 & 0 & 0\\1 & 2 & 1\end{pmatrix} \]
图像的每一个像素的横向和纵向梯度用一下公式近似计算
\[ S=\sqrt{(S_x\circ I)^2+(S_y\circ I)^2} \]
然后梯度方向用计算如下
\[ \theta=\arctan\frac{S_y\circ I}{S_x\circ I} \]
我们在用Sobel算子计算方向的时候调用的是cv2.Sobel函数。

Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) -> dst

src表示输入图像,dx,dy表示x和y方向导数的阶,ksize表示Sobel算子的大小,只能为奇数,scale表示决定是否进行正规化处理,默认可以不填这个参数,ddepth,delta和borderType和之前的一样。

cv2.Sobel算子给出的计算公式如下:
\[ \texttt{dst} = \frac{\partial^{xorder+yorder} \texttt{src}}{\partial x^{xorder} \partial y^{yorder}} \]
这个函数常见的调用是

xorder = 1, yorder = 0, ksize = 3
xorder = 0, yorder = 1, ksize = 3

对应上面两个矩阵。调用这个函数

sobel_horizontal = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobel_vertical = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)

我们得到如下两张图片

水平方向

垂直方向

我们再计算一个两个方向导数都取\(1\)的图像。

sobel=cv2.Sobel(img, cv2.CV_64F, 1, 1, ksize=3)

Sobel算子需要注意事项

需要注意的是,在sobel中,因为求导之后会有负数,所以如果原图像是\(8\)位无符号数,数据就会出现截断,所以需要用最小16位的数据格式。数据改成\(16\)位或以上再进行Sobel算子计算边缘之后,还需要用convertScaleAbs将图像转会uint8的形式。

convertScaleAbs(src[, dst[, alpha[, beta]]]) -> dst

convertScaleAbs函数主要进行三个操作,简单缩放,取绝对值,将数据转化为无符号\(8\)位。alpha是数据缩放系数,beta是偏移量。

计算两个方向的Sobel算子之后,再用addWeighted函数将其叠加起来,

addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]]) -> dst

这个函数是这样计算的
\[ \texttt{dst} (I)= \texttt{saturate} ( \texttt{src1} (I)* \texttt{alpha} + \texttt{src2} (I)* \texttt{beta} + \texttt{gamma} ) \]
alpha和beta分别是两个方向图片所占权重,gamma是偏移量。

因此我们也可以在之前生成横向和纵向图片的基础上这样先转换回uint8。

sobel_horizontal = cv2.convertScaleAbs(sobel_horizontal)
sobel_vertical = cv2.convertScaleAbs(sobel_vertical)

再叠加权重

sobel = cv2.addWeighted(sobel_horizontal,0.5,sobel_vertical,0.5,0)

于是我们生成了下列图片。

比之前两个方向各求一次导得到的清楚很多。

Scharr算子

为了改进索伯算子,沙尔提出了更大的\(5\times 5\)核心,但是最常使用的是如下一组水平和垂直方向滤波器。
\[ S_x=\begin{pmatrix}3 & 0 & -3\\10 & 0 & -10\\3 & 0 & -3\end{pmatrix} S_y=\begin{pmatrix}3 & 10 & 3\\0 & 0 & 0\\-3 & -10 & -3\end{pmatrix} \]

Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]]) -> dst

参数都和之前的Sobel算子相同。

官方文档也标注以下两个语句是相同的。

Scharr(src, dst, ddepth, dx, dy, scale, delta, borderType)
Sobel(src, dst, ddepth, dx, dy, FILTER_SCHARR, scale, delta, borderType)

我们还对对这张图片进行之前对Sobel算子进行的操作。考虑到数据类型是uint8,我们将其转化为64位浮点。

img_h=cv2.Scharr(img,cv2.CV_64F,1,0)
img_v=cv2.Scharr(img,cv2.CV_64F,0,1)
img_h=cv2.convertScaleAbs(img_h)
img_v=cv2.convertScaleAbs(img_v)
scharr = cv2.addWeighted(img_h,0.5,img_v,0.5,0)

得到结果

这张是从彩图中得到的。

这张来自于灰度图像。

可以看出明显比Sobel算子清晰了很多,但相对的,无关的噪点也增加了很多。

Laplacian算子

拉普拉斯算子就是欧式空间中一个函数梯度的散度给出的微分算子,通常写作\(\Delta\)\(\nabla^2\)\(\nabla\cdot\nabla\)

函数的梯度就是其在欧式空间某个点的方向。譬如在三维欧式空间中,函数\(f(x,y,z)\)有梯度
\[ \nabla f=\frac{\partial f}{\partial x}i+\frac{\partial f}{\partial y}j+\frac{\partial f}{\partial z}k=(\frac{\partial f}{\partial x},\frac{\partial f}{\partial y},\frac{\partial f}{\partial z}) \]
上面的梯度构成一个向量场,因此我们可以对其进行散度运算。散度为
\[ \bf div \nabla f=\nabla\cdot\nabla f=\frac{\partial f_x}{\partial x}+\frac{\partial f_y}{\partial y}+\frac{\partial f_z}{\partial z}=\frac{\partial^2 f}{\partial x^2}+\frac{\partial^2 f}{\partial y^2}+\frac{\partial^2 f}{\partial z^2} \]
考虑到图像是二维离散空间,我们来看二维空间的Laplacian算子,其具有如下形式
\[ \Delta f=\frac{\partial^2 f}{\partial x^2}+\frac{\partial^2 f}{\partial y^2} \]
一维空间中的Laplacian算子可以表示为具有如下形式
\[ \frac{\partial^2 f}{\partial x^2}=(f(x+1)-f(x))-(f(x)-f(x-1))\\ \frac{\partial^2 f}{\partial y^2}=(f(y+1)-f(y))-(f(y)-f(y-1)) \]
我们将其相加
\[ \Delta f=\frac{\partial^2 f}{\partial x^2}+\frac{\partial^2 f}{\partial y^2}=(f(x+1,y)-f(x,y))-(f(x,y)-f(x-1,y))+\\(f(x,y+1)-f(x,y))-(f(x,y)-f(x,y-1))=\\f(x+1,y)+f(x-1,y)+f(x,y+1)+f(x,y-1)-4f(x,y) \]
于是我们得到了如下形式的滤波器
\[ \begin{pmatrix}0 & 1 & 0\\1 & -4 & 1\\0 & 1 & 0\end{pmatrix} \]
显而易见的,Laplacian算子是一个二阶算子,也明显可以用于边缘检测。

我们在cv2中调用Laplacian函数进行边缘检测。

Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]]) -> dst

Laplacian函数的参数和上面的Sobel参数一样,而且这个函数计算的矩阵就是我们上面得到的矩阵。我们用Laplacian算子进行计算。

lap=cv2.Laplacian(img,cv2.CV_64F,ksize=3)

得到图片如下

可以看出比Sobel算子是好了不少,我个人感觉比Scharr算子也好点,但是这个见仁见智,但是Laplacian算子还是有许多噪点,为了改善这一问题,Canny算子应运而生。

Canny算子

Canny算子的算法是这样的,

  • 首先应用高斯滤波器,对图像进行平滑处理,来移除图像中的噪点,
  • 然后计算亮度的梯度。计算梯度的滤波器有四个,水平垂直和对角线上的边缘检测。这些滤波器可能是之前的Sobel,Scharr或Roberts滤波器。
  • 进行非最大值抑制,来找到最可能是边缘的边缘,即忽略其他可能不是边缘但滤波器找到的部分。
  • 二次过滤,设定高阈值和低阈值。边缘梯度高于高阈值,则记为强边缘像素,处于两个阈值中间,则记为弱边缘像素,低于低阈值,则将其忽视。这两个阈值的设定取决于经验。
  • 迟滞边缘追踪,追踪弱边缘像素附近的点,如果存在强边缘像素,则将其保留。

由于这几步随便哪个拿出来都能写一堆,我就不在这篇博客中写了,以后再写。在cv2中我们调用Canny函数。

Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) -> edges

image就是输入图像,threshod1和2分别是低阈值和高阈值,apertureSize 是Sobel算子的大小,L2gradient指的是用欧式距离还是曼哈顿距离,默认L2gradient=False,即选择曼哈顿距离,而L2gradient=True,则选择欧式距离。我们用50,250做阈值,欧式距离计算强度,来测算边缘。

can=cv2.Canny(img,50,250,L2gradient=True)

得到图像如下

我觉得我的阈值选的有问题,反正结果比Laplacian算子那个还不如。大家也可以试试别的阈值。

除了上述这些边缘检测算子还有很多算子,这里先不介绍了,以后再用到可能会说。

一些图像滤波器的其他应用

动态模糊

动态模糊指照片或一系列帧中移动的物体的明显条纹,类似动画或者电影。一般动态模糊发生时,照片处在长曝光或者物体迅速移动的状态。

我们可以用filter2D做动态模糊,譬如用一个指向某个方向的矩阵做滤波器,这样某一列的值都是该列的平均值。

譬如我们用如下矩阵做滤波器
\[ \frac{1}{15}\begin{pmatrix}0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0\\5 & 4 & 3 & 2 & 1\\0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0& 0\end{pmatrix} \]

kernel_motion_blur=np.zeros((5,5))
kernel_motion_blur[2]=[5,4,3,2,1]
kernel_motion_blur=kernel_motion_blur/15
img_mb=cv2.filter2D(img,-1,kernel_motion_blur)

以上代码生成如下矩阵。将其带入filter2D函数。

看起来不是很明显,但是糊了很多(hhh。

我们试一个大一点的滤波器。

kernel_motion_blur=np.zeros((15,15))
kernel_motion_blur[2]=np.ones(15)
kernel_motion_blur=kernel_motion_blur/15
img_mb=cv2.filter2D(img,-1,kernel_motion_blur)

这就很有感觉了,卫星确实也很快,但是跟着它拍的相机一般速度也会和它一致,所以这照片也很鬼畜。

简而言之,想创造出动态模糊的效果,我们就弄一个单方向的低通滤波器。

锐化

使用锐化滤波器,使得图片的边缘更加明显。增强图画边缘时用锐化滤波器就很不错。以下图片分别用三个滤波器滤波,我先给出三个滤波器的矩阵
\[ \bf kernel_1=\begin{pmatrix}-1 & -1 & -1\\-1 & 9 & -1\\-1 & -1 & -1\end{pmatrix}\ \ \ \bf kernel_2=\begin{pmatrix}-1 & -1 & -1\\-1 & 9 & -1\\-1 & -1 & -1\end{pmatrix}\\ \bf kernel_3=\frac{1}{8}\begin{pmatrix}-1 & -1 & -1 & -1 & -1\\-1 & 2 & 2 & 2 & -1\\-1 & 2 & 8 &2 & -1\\-1 & 2 & 2 & 2 & -1\\-1 & -1 & -1 & -1 & -1\end{pmatrix} \]
代码为

kernel_1=np.array([[-1,-1,-1],[-1,9,-1],[-1,-1,-1]])
kernel_2 = np.array([[1,1,1],[1,-7,1],[1,1,1]])
kernel_3 = np.array([[-1,-1,-1,-1,-1],[-1,2,2,2,-1],[-1,2,8,2,-1],[-1,2,2,2,-1],[-1,-1,-1,-1,-1]])/8.0
output_1 = cv2.filter2D(img, -1, kernel_1)
output_2 = cv2.filter2D(img, -1, kernel_2)
output_3 = cv2.filter2D(img, -1, kernel_3)
h1=np.hstack((img,output_1))
h2=np.hstack((output_2,output_3))
h=np.vstack((h1,h2))

得到堆叠后的图像

可见效果最好的是第四个,因为采用了类似高斯平滑的边缘强化滤波器,在强化边缘的时候看起来也更自然。前面两个都看起来太有人工痕迹了。

压印浮凸

压印浮凸是一种设计方式,就是把纸上的某些花纹图案印的凹陷下去或者凸起来。

可以用以下三组滤波器做压印浮凸。
\[ \bf kernel_1=\begin{pmatrix}0 & -1 & -1\\1 & 0 & -1\\1 & 1 & 0\end{pmatrix}\ \ \ \bf kernel_2=\begin{pmatrix}-1 & -1 & 0\\-1 & 0 & 1\\0 & 1 & 1\end{pmatrix}\ \ \ \bf kernel_2=\begin{pmatrix}1 & 0 & 0\\0 & 0 & 0\\0 & 0 & -1\end{pmatrix} \]
这个原理就是把每一个像素替换成其阴影或者高亮。

kernel_1 = np.array([[0,-1,-1],[1,0,-1],[1,1,0]])
kernel_2 = np.array([[-1,-1,0],[-1,0,1],[0,1,1]])
kernel_3 = np.array([[1,0,0],[0,0,0],[0,0,-1]])
output_1 = cv2.filter2D(img1, -1, kernel_1) + 128
output_2 = cv2.filter2D(img1, -1, kernel_2) + 128
output_3 = cv2.filter2D(img1, -1, kernel_3) + 128

得到下图

反正我也不是做这个的,具体原理我也不是清楚。

腐蚀与膨胀

腐蚀和膨胀是数学形态学的基本算子,数学形态学是一门建立在格伦和拓扑学上的图像分析学课。我们用cv2里面的erode和dilate来做腐蚀和膨胀。这两个函数都有迭代次数这个参数,迭代次数越多,腐蚀或者膨胀的就越多。我们用维度为\(5\times5\)的数值格式为uint5的矩阵来做卷积核。

kernel = np.ones((5,5), np.uint8)
img_erosion = cv2.erode(img, kernel, iterations=1)
img_dilation = cv2.dilate(img, kernel, iterations=1)

得到图像如下

可以看出腐蚀和膨胀其实改变的是外围像素点。

晕影

在射影和光学领域,晕影指的是图像的外围部分亮度或饱和度比中心区域低。我们可以用一个高斯滤波器来过滤图像的每一个通道,来生成这种图片。我们用getGaussianKernel来生成高斯滤波器。

getGaussianKernel(ksize, sigma[, ktype]) -> retval

其计算公式如下
\[ G_i= \alpha *e^{-(i-(ksize-1)/2)^2/(2\sigma^2)}\\ i=0...ksize-1,\alpha是缩放标量,\sum_iG_i=1 \]
其中ksize是光晕的大小,\(\sigma\)是正态分布的方差,默认计算\(\sigma = 0.3\times((ksize-1)\times0.5 - 1) + 0.8\)。ktype是过滤器的数据类型,可以有CV_32F或CV_64F。这个函数给出\(ksize\times1\)的矩阵\(G\)。我们先用这个函数生成横向的过滤器

kernel_x=cv2.getGaussianKernel(cols,200)
kernel_y=cv2.getGaussianKernel(rows,200)

然后再讲两个过滤器进行乘法运算,再将得到的新向量正规化操作

kernel=kernel_y*kernel_x.T
kernel=255*kernel/np.linalg.norm(kernel)

np.linalg.norm是求范数的函数,其参数如下

np.linalg.norm(x, ord=None, axis=None, keepdims=False)

x表示矩阵,ord表示范数的类型,取1是曼哈顿距离,取2是欧式距离,取np.inf相当于取行和绝对值的最大,axis表示取向量的方向,取0表示求列向量范数,取1表示求行向量,取None表示求矩阵范数,keepding表示是否保持矩阵的二维特性,默认不保持。我们通过先将求得的卷积核进行正则化,使其值都在\([0,1]\)区间内,再乘255,以实现颜色空间的正则化。然后我们用得到的新矩阵对图像的三个通道进行处理。

h=np.zeros(img.shape)
for i in range(3):
    h[:,:,i] = img[:,:,i]*kernel

得到结果如下

如果想要移动焦点,我们可以改变滤波器,譬如把滤波器矩阵变为原来的数倍大小,然后再进行裁剪。我们就不在此进行赘述了。

增强图像对比度

当我们在黑暗环境中拍摄时,某些像素点的值可能趋于\(0\),这种情况下我们无法识别图像中的物体。但尽管像素点的值绝对意义上趋近于0,但相对的还是有去别的,因此我们可以利用这种相对区别来看清图像,也就是增强图像的对比度。我们用直方图均衡化的方法来增强图像全局的对比度。

考虑一个离散的灰度图像\(I\),用\(n_i\)表示像素\(i\)出现的次数,这样图像中灰度为\(i\)的像素出现的概率为
\[ p_x(i)=p(x=i)=\frac{n_i}{\sum_in_i},0\leq i\leq L-1 \]
L是图像中数值最大的像素值,一般是\(256\)\(\sum_in_i\)为图像中的总像素数。其累计分布函数为
\[ F_x(i)=\sum_{j=0}^ip_x(x=j) \]
我们根据累计分布函数,将其映射在标准化后为\([0,1]\)的空间内,再根据映射后的值取灰度值,然后得到均衡化后的图像。我们在cv2中调用equalizeHist函数。

equalizeHist(src[, dst]) -> dst

该函数的参数为输入图像。该函数运行机制也和我们说的一样,计算输入图像的直方图\(H\),标准化直方图,以使得直方图中柱子之和为255。计算直方图的积分\(H'_i = \sum _{0 \le j < i} H(j)\),然后通过查找\(H'\)的方式转换\(dst_{(x,y)} = H'((src)_{(x,y)})\)。简而言之,我们可以试做一个表格。

原色度 出现数目 出现概率 累进概率 对应取整后颜色
1 4 0.25 0.25 64
5 10 0.675 0.875 223
30 2 0.125 1 255

就像上表一样。

salut_histogram = cv2.equalizeHist(img)

得到下图

对比度强确实强了,就是杂音太多。可以先做一个高斯平滑再做对比度增强。得到这样的

还可以做一些降噪工作,但是我们这里就不瞎说了。

在彩色图像方面,我们不能在rgb色域上做均衡化,因为这样会产生奇异的点,所以我们在HSL或者HSV空间内做均衡,然后再转化回来。

img_hsv=cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
img_hsv[:,:,1]=cv2.equalizeHist(img_hsv[:,:,1])
img_new=cv2.cvtColor(img_hsv,cv2.COLOR_HSV2BGR)

得到下图

对比度确实增强了不少,但我觉得还是原来那个好看。

部分内容参考OpenCV with Python By Example,作者Prateek Joshi。

猜你喜欢

转载自www.cnblogs.com/Komnenos/p/opencv_filter.html