opencv Notes (2) image filter

opencv Notes (2) image filter

The image filter

In the digital signal processing filter is actually quite common, especially the sound processing inside, because sometimes the conversion and frequency domain, so the frequency domain may be made in some operations, such as filtering out certain frequencies wave with a filter, high pass and low-pass filters, respectively filter out low frequency sound waves and a high frequency sound waves.

In the image fields have the same operation, the image is a low pass filter to filter out pixel varies a lot, in other words, fuzzy processing, and the high-pass filter, in the same way, for the image sharpening process.

We can call the image filter convolution kernel is, in fact, is the convolution of a common neural network components. In the following we write a \ (3 \ times3 \) convolution kernel.
\ [\ Begin {pmatrix} c_1
& c_2 & c_3 \\ c_4 & c_5 & c_6 \\ c_7 & c_8 & c_9 \ end {pmatrix} \] new convolving the image convolution operation of the collation image, and then output. Can be expressed as the following mathematical form:
\ [F: Image \ rarr Image '\\ \ A_} pmatrix the begin {1,1} {1,2} {& A_ & \ A_ & cdots. 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}'
In other words, the number of product operation carried out by the convolution kernel and the picture is consistent with the convolution kernel size of the part, then the number of operations resulting product as a new pixel.

Blurring the image

As the name suggests, it is to reduce blurring between a pixel value of jumping points, in other words, taking the average of the range of possible (moving average is also a financial process, increase smooth curve). We can do the following convolution kernel fuzzy processing.
\ [\ Frac {1} {
9} \ begin {pmatrix} 1 1 \\ 1 1 & & 1 & & 1 \\ 1 & 1 & 1 \ end {pmatrix} \] when we do the convolution processing opencv use the function filter2D

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

src represents image (source) input, ddepth data indicating the type of storage, such as unsigned char CV_8U, unsigned short character CV_16U ...... When the value \ (- 1 \) while keeping the original image. represents the convolution kernel core, typically a \ (n \ times n \) matrix, dst indicates the input image with the same size of the output image, anchor represents a reference point of the core, the reference point is the convolution kernel and the pixel processing performed position consistent point, delta represents an optional offset value, borderType edge pixel representation of the process, like the empty filling comprising various pixel values. detailed

Said the official documents of this function is actually calculated is relevant, rather than convolution.
\ [{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}) \] in fact a convolution correlation coefficient is, they are in such a relationship
\ [f (t) \ circ g
(t) = f * (- t) \ circ g (t) \] so if we want to calculate the real convolution, we need to flip the picture, and a new anchor set

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

We also do an example with the previous picture

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

After then produced two pictures contrast np.hstack

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

If \ (3 \ times 3 \) ambiguity convolution kernel is not obvious, we look at \ (7 \ Times 7 \) .

Filter2D addition function, the function can also be used blur, blur function has the following parameters.

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

Src which is the input picture, ksize is the convolution kernel size, and other parameters are the same filter2D.

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

Pictures of edge detection

边缘检测就是检测数字图像中亮度变化明显的点,有些边缘检测是基于亮度的一阶导数,有些则基于二阶导数,一阶倒数是原始亮度函数的梯度。我们可以这样计算此导数
\[ 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

The parameters of the function is the input image. The function and operation mechanism also as we say, the input image histogram calculation \ (H \) , histogram normalized such that the sum of histogram column 255. Computing the histogram integral \ (H'_i = \ {0 SUM _ \ Le J <I} H (J) \) , then by looking (H '\) \ way to convert \ (dst _ {(x, y) = H} '((the src) _ {(X, Y)}) \) . In short, we can try to do a form.

Primary color degree The number appears Probability Progressive probability Colors correspond to rounding
1 4 0.25 0.25 64
5 10 0.675 0.875 223
30 2 0.125 1 255

Like the tables.

salut_histogram = cv2.equalizeHist(img)

FIG obtain the

Contrast strong indeed strong, that is too much noise. You can do a Gaussian smoothing do contrast enhancement. Get this

Noise can also do some work, but we are here is not a nonsense.

In terms of color images, we can not do equalized in the rgb color gamut, as this will produce singular point, we do so in a balanced HSL or HSV space, then converted back.

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)

FIG obtain the

Contrast really increased a lot, but I think it turned out that good-looking.

Part reference OpenCV with Python By Example, author Prateek Joshi.

Guess you like

Origin www.cnblogs.com/Komnenos/p/opencv_filter.html