图像平滑

图像平滑

除去一些噪点,有些可以保留边界,比如中值滤波,导向滤波,双边滤波

二维离散卷积

这个卷积我真的看不出哪里和卷有关系,主要是三种卷积,full,same,valid(有效的)。他们的关系是valid包含于same(当然卷积核要小于被卷积的矩阵以保证valid卷积存在),same包含于full中

full卷积

想象两个矩阵有贴合的部分,将贴合的部分对应相乘再相加,卷积核不断移动结果保存再一个新的矩阵中。下面开始定量分析:
假设被卷积的矩阵的大小为H1xW1(H:height,等于行数),卷积核的大小为H2xW2,那么有:

  1. 卷积后的矩阵大小为H1+H2-1 x W1+W2-1 ,这个这个很好理解,卷积一定要有重合部分,那么当卷积核在某行上移动时,一共有W1+W2-1种不同的位置情况,结果按从左到右移动的顺序排列就是W1+W2-1个,在竖直方向上也是如此。
  2. 这种移动的算卷积的方法等价于另一种奇怪的算法,下面给图:
    图像平滑_第1张图片

图像平滑_第2张图片
过程就是先把两个矩阵从下和右扩展成结果矩阵的大小,取卷积核的第一行循环一遍(以最后一个跑到第一个这样的顺序),记作G0,同理取卷积核的下面几行进行相同的操作记作G1……GH-1,然后将G0……GH-1这H块再以相同的方式循环一遍,这样就得到了G,ip就很简单了直接看图吧,最后两个矩阵相乘再排开就是结果。呃,至于怎么得到这个算法的,我不知道了。

哦,忘了给出full卷积的记法了:
图像平滑_第3张图片

valid卷积

这个要求卷积核不能跑出被卷积的矩阵外面(就是卷积核要全部被包围),所以要求卷积核行数列数都要小于等于被卷积的矩阵,算法和full一样。
着重讲一下valid和full的数量关系:
我们从零开始数行数,列数,那么有:
1.图像平滑_第4张图片
首先我们要知道valid的大小是多少,就看行数,列数的算法和行数一样,full一共有H1+H2-1个,减去多出来的部分,就是减掉两个(H2-1),结果是H1-H2+1,与上面相符,然后由于valid在full中是连续的,我们只要找到从那开始就行了,显然full的前H2-1个是无效的,所以从H2-1(标号)开始(包含H2-1,注意标号从零开始数)

same卷积

后面平滑算法要的好像都是这个
same的特征就是算完后的矩阵大小和原来的一样(对于图像来说大小不变)
计算过程有点不一样,先看一个概念,锚点,简单说就是取卷积核的一个固定位置,有什么用看下面的计算过程:
1.取定锚点,将锚点与被卷积矩阵的一个位置重合,重合部分对应相乘并相加(锚点那个也要算,还有卷积核中没有东西对应的情况后面讲),结果存在锚点对应的位置
2.锚点移遍被卷积的矩阵,结果存在结果矩阵的对应位置(锚点重合的位置),注意每一次计算都是对原矩阵进行

关于卷积核中没有东西对应的情况,有几种处理办法,opencv也提供了对应的函数中的选项:

图像平滑_第5张图片
图像平滑_第6张图片

python

就写了常用的几个:

import cv2 as cv
import numpy as np
a=np.array([[1,2,3],[4,5,6],[7,8,9]],dtype=np.uint8)
replicate=cv.copyMakeBorder(a,2,2,2,2,cv.BORDER_REPLICATE)#边界复制
constant=cv.copyMakeBorder(a,2,2,2,2,cv.BORDER_CONSTANT,value=3)#常数扩充,最后一个参数是用来填充的常数,养成写参数名的好习惯
reflect=cv.copyMakeBorder(a,2,2,2,2,cv.BORDER_REFLECT)#反射扩充
print(replicate)
print('\n')
print(constant)
print('\n')
print(reflect)
print('\n')
cv.waitKey(0)

图像平滑_第7张图片
怎么填充看结果就行了,至于用哪个要看具体的平滑算法是怎么样的
same与full的关系(这个很重要,当我们想要自选锚定点时就要先算full卷积再截出same卷积部分,没办法,算卷积的函数只能选默认锚定点)
图像平滑_第8张图片
图像平滑_第9张图片
分析思路跟上面valid与full一样,看行数:长度为H(这里长度直接知道),从H2-1-kr(编号)开始,不讲了。

用于算卷积的函数

给出函数名和same模式时默认锚点的取法说明:
图像平滑_第10张图片
这个函数在Scipy包中

python
from scipy import signal
import numpy as np
import cv2 as cv
k=np.array([[1,1],[1,1]],dtype=np.uint8)
i=np.array([[2,2],[2,2]],dtype=np.uint8)
full=signal.convolve2d(i,k,mode='full')#先算full卷积
same=full[2-1-0:2-1-0+2,2-1-0:2-1-0+2]#截取same卷积
print(full)
print('\n')
print(same)

[[2 4 2]
[4 8 4]
[2 4 2]]

[[8 4]
[4 2]]

可分离卷积核

就是卷积核可以拆成两个卷积(一维竖直与一维水平)的full卷积,然后根据卷积的结合律,然后交换律(这个只对一维的成立),把卷积分成两次卷积,为什么要这么搞,因为分开算可以加快运算速度,证明就算了,不会也不想写了,背就完事了。
图像平滑_第11张图片

先看full卷积的

图像平滑_第12张图片
很简单,也用不太到

same卷积的

性质和full是一样的,但有个问题,就是边界填充,这里给出结论,只有为常数填充时,直接算和拆开算才会一样,不是常数填充时,凡是接触到边界的值都可能会不一样,就是上面、下面(H2-1)/2行,左边右边(W2-1)/2列可能会不一样,(呃,上面的卷积核是奇数x奇数,偶数我不会了)当卷积核较小,被卷积矩阵大小较大时这些无关紧要

高斯卷积核与均值卷积核都是可分离的

高斯平滑

由于概率论还没学,所以后面的数学原理我就无能为力了
图像平滑_第13张图片
要得到一个奇数乘奇数的高斯平滑卷积核,锚点在中间,首先要知道这个卷积核它的各个数与卷积核的大小和sigma有关而与原矩阵无关,然后我们看各个点的值具体是怎么算的,代入上面的公式,算完后再归一化就算完成了。

理解的话,呃,从直观的角度讲,离中心越近他的权重就越大,那么我对一个图像做高斯平滑后每个像数点更接近它周边的情况(比均值平滑更精确),这样那些孤立的点(比如一片黑色中出现一点白)就会被消除,但对于明显的边界会使边界模糊,保边效果比较差。

还有就是计算时由于要归一化,所以那个计算公式的系数可以去掉。然后高斯卷积核是中心对称的。
图像平滑_第14张图片
上图给出了高斯卷积核分离公式,可以先进行竖直方向上的高斯平滑再进行水平方向上的

python实现

import numpy as np
import cv2 as cv
from scipy import signal
src=cv.imread(r'C:\Users\19583\Desktop\3.jpg',cv.IMREAD_GRAYSCALE)
src=cv.resize(src,(500,500))
gk1=cv.getGaussianKernel(3,2,cv.CV_64F)#水平方向的高斯卷积核,这是一维的
gk2=cv.getGaussianKernel(3,2,cv.CV_64F)#参数讲一下,第一个是大小(一维的),第二个是sigma的值,后面是结果的类型
gk2=np.transpose(gk2)#竖直方向的高斯卷积核,这是一维的
a=src.copy()
a=signal.convolve2d(a,gk1,mode='same',boundary='fill')#采用常数填充边界的方式
a=signal.convolve2d(a,gk2,mode='same',boundary='fill')#要输入二维数组,之前直接把三通道a输进来了
a=a/255#a的dtype是double,所以归一化一下
cv.imshow('src',src)
cv.imshow('a',a)
cv.waitKey(0)


还有用二项分布逼近高斯分布的东西,等概率论学了再来补,有上面的足够了

opencv中有现成的高斯平滑函数:
GaussianBlur

import cv2 as cv
src=cv.imread(r'C:\Users\19583\Desktop\3.jpg',cv.IMREAD_GRAYSCALE)
src=cv.resize(src,(500,500))
dst=cv.GaussianBlur(src,(3,3),2,2,borderType=cv.BORDER_CONSTANT)#输入矩阵,高斯卷积核大小,水平方向的σ,竖直方向的σ,边界类型
cv.imshow('src',src)
cv.imshow('dst',dst)
cv.waitKey(0)

均值平滑

将卷积核覆盖的值取平均放到中间
卷积核很容易构造,一般取奇数乘奇数,这样锚点在中间。

关于计算优化

可以将卷积核拆开,但是这里有更好地方法,一种动态规划的算法
先了解一下矩阵的积分:
对于矩阵中(r,c)位置的元素,将它上面的、左边的值全部相加,结果放到它的位置,注意每次操作都是对原矩阵进行
图像平滑_第15张图片
图像平滑_第16张图片
像图上说的,可以由积分后的矩阵快速得到某个区域的所有元素之和

python

import cv2 as cv
import numpy as np
src=np.array([[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1]],dtype=np.uint8)
def integral(src):
    rows,cols=src.shape[:2]
    inter=np.ones((rows,cols),dtype=np.float32)
#第零列为原来的值,后面的每个元素都是相同行的左边所有元素之和,这样每个元素上面同列的每个元素就是它们左边的同行元素的和
    for r in range(rows):
        for c in range(cols):
            if c==0:
                inter[r][c]=src[r][c]
            else:
                inter[r][c]=inter[r][c-1]+src[r][c]
    intec = np.ones((rows, cols), dtype=np.float32)
# 这里就是把每个元素上面的一条加起来,这样就得到了原矩阵的积分
    for c in range(cols):
        for r in range(rows):
            if r == 0:
                intec[r][c] = inter[r][c]
            else:
                intec[r][c] = intec[r-1][c] + inter[r][c]
    return intec
dst=integral(src)
print(dst)
#下面进行快速均值平滑
a=cv.imread(r'C:\Users\19583\Desktop\3.jpg',cv.IMREAD_GRAYSCALE)
a=cv.resize(a,(500,500))
def quickMeanBlur(img,size):#传入图像,卷积核
    img1=cv.copyMakeBorder(img,int((size.shape[0]-1)/2),int((size.shape[0]-1)/2),int((size.shape[1]-1)/2),int((size.shape[1]-1)/2),borderType=cv.BORDER_REFLECT)#边界扩充,这里用镜像填充好
    #获得图像的积分
    inteimg=integral(img1)
    #开始均值平滑
    H=int((size.shape[0]-1)/2)#类型注意一下
    W=int((size.shape[1]-1)/2)
    meanblur=np.ones(img.shape,dtype=np.float32)
    for r in range(H,int(H+img.shape[0]),1):
        for c in range(W,int(W+img.shape[1]),1):
            meanblur[r-H][c-W]=(inteimg[r+H][c+W]-inteimg[r-H-1][c+W]-inteimg[r+H][c-W-1]+inteimg[r-H-1][c-W-1])/(size.shape[0]*size.shape[0])
    return meanblur
k=np.array([[1,1,1],[1,1,1],[1,1,1]],dtype=np.uint8)
dst=quickMeanBlur(a,k)
dst=dst/255
cv.imshow('a',a)
cv.imshow('dst',dst)
cv.waitKey(0)

[[ 1. 2. 3. 4. 5.]
[ 2. 4. 6. 8. 10.]
[ 3. 6. 9. 12. 15.]
[ 4. 8. 12. 16. 20.]
[ 5. 10. 15. 20. 25.]]

然后关于均值平滑,opencv提供了现成的函数

import cv2 as cv
import numpy as np
a=cv.imread(r'C:\Users\19583\Desktop\3.jpg',cv.IMREAD_GRAYSCALE)
a=cv.resize(a,(500,500))
dst1=cv.boxFilter(a,8,(3,3),normalize=1)#图,位深度,卷积核尺寸,是否归一化
dst2=cv.blur(a,(3,3),borderType=cv.BORDER_REFLECT)#图,卷积核尺寸,边界扩充类型
cv.imshow('a',a)
cv.imshow('dst1',dst1)
cv.imshow('dst2',dst2)
cv.waitKey(0)

中值平滑

这个就是取一个奇数乘奇数的卷积核,进行类似same卷积的过程,然后在卷积核覆盖的区域中找出中位数放到锚点上,这样的算法具有保边性,对椒盐噪声的去除效果较好

import cv2 as cv
import numpy as np
import random
a=cv.imread(r'C:\Users\19583\Desktop\3.jpg',cv.IMREAD_GRAYSCALE)
a=cv.resize(a,(500,500))
for i in range(500):#生成有椒盐噪声的图
    randr=random.randint(0,499)
    randc = random.randint(0, 499)
    a[randr][randc]=0
def medianBlur(img,size):
    rows,cols=img.shape
    H=int((size[0]-1)/2)
    W=int((size[1]-1)/2)
    medianblur=np.ones(img.shape,img.dtype)
    for r in range(rows):
        for c in range(cols):
            # 判断边界
            rTop = 0 if r - H < 0 else r - H
            rBottom = rows - 1 if r + H > rows - 1 else r + H
            cLeft = 0 if c - W < 0 else c - W
            cRight = cols - 1 if c + W > cols - 1 else c + W
            # 取中值的区域
            region = img[rTop:rBottom + 1, cLeft:cRight + 1]
            medianblur[r][c]=np.median(region)
    return medianblur
dst=medianBlur(a,(3,3))
cv.imshow('dst',dst)
cv.imshow('a',a)
cv.waitKey(0)

opencv中有现成的函数;

import cv2 as cv
import numpy as np
a=cv.imread(r'C:\Users\19583\Desktop\3.jpg',cv.IMREAD_GRAYSCALE)
a=cv.resize(a,(500,500))
dst=cv.medianBlur(a,3)#图,大小
cv.imshow('dst',dst)
cv.imshow('a',a)
cv.waitKey(0)

双边滤波

联合双边滤波

导向滤波

你可能感兴趣的:(opencv算法与使用,opencv)