图像锐化和边缘检测算子分析与实现

边缘检测算子     

在总结边缘检测算子之前,先记录一个Python函数库的用法Pillow--ImageFilter,该函数集合了常用滤波函数,例如高斯滤波,中值滤波等。它内部帮你处理了由于滤波或者其他操作造成的数值越界等问题,比如说,Pillow的Image类打开的图片都是uint8 类型的,在做完滤波后,有些值超过255,就变成个数,有些值小于0,就会突变成二百多。可以采用cv2.convertScaleAbs()进行处理,该函数会将大于255的数值修改成255,负数变成绝对值。并且数值全都为无符号uint8型

    a = np.array([254, 255, 256, 257, -1, -2])
    print(a)
    b = a.astype(np.uint8)
    print(b)
    c = cv2.convertScaleAbs(a)
    print(c)
#输出:
[254 255 256 257  -1  -2]
[254 255   0   1 255 254]
[[254]
 [255]
 [255]
 [255]
 [  1]
 [  2]]

因此,本文将采用多种方法来避免这种情况。一个是整型的类型转换,在图像处理后,将uint8转成int。或者处理的时候直接采用浮点类型。在opencv读取默认时uint8,并且如果采用浮点类型,默认0-1的数值,即,如果出现大于1,而又是浮点类型,那么该像素就显示白色了,白色像素点在uint8中是255, 在浮点中1.0为白色。因此,在加载的时候 浮点类型 一定要注意浮点表示和整型的关系。总之,cv2在对图像矩阵处理时,如果其数值为浮点型,这加载的像素点大小范围[0.0, 1.0].如果为整型,这范围为[0,255]. 如果加载进来的是浮点,而不小心弄成[0,255]这些数,那么图像就很可能是全白色

ImageFilter

先举一个高斯模糊函数例子来抛出ImageFilter基本用法:


from PIL import Image
from PIL import ImageFilter
    
src_path = '/home/szx/Desktop/tt/1.jpg'
img = Image.open(src_path)
img = img.convert('L')
im_filter = img.filter(ImageFilter.GaussianBlur(radius=5))
img.convert('L')将rgb其转换成灰度图,'L'对应的参数还有'RGB'。高斯滤波核的半径为5,类似的滤波核还有:
UnsharpMask、Kernel、RankFilter、MedianFilter、MinFilter、MaxFilter、ModeFilter、BoxBlur、UnsharpMask

剩下来的还有一些其实是kernel函数的变形,比如说:

class EMBOSS(BuiltinFilter):
    name = "Emboss"
    # fmt: off
    filterargs = (3, 3), 1, 128, (
        -1, 0, 0,
        0,  1, 0,
        0,  0, 0,
    )

他等价于

im_filter = img.filter(ImageFilter.Kernel((3,3),(-1, 0, 0, 0, 1, 0, 0, 0, 0), scale=1,offset=128))

总之都能用ImageFilter.Kernel代替。那么Kernel.init(self, size, kernel, scale=None, offset=0)函数的几个参数的表示意义为:

size: 卷积核的尺寸,kernel:卷积核的具体值,通过一维来表示。scale为放大系数。offset,偏移量,为在原来值的基础上加上该值。

比如经过kerne卷积核以后,图片的某个像素值为:5,如果sacle=2,offset=128,那么像素值最终为:5*2+128 = 138

不过kernel的尺寸必须为(3,3) 或 (5,5)。如果卷积核为其他尺寸需要用到opencv了,后面会介绍其用法。

边缘提取算子

算子的推到过程,有的利用二维函数的偏导数,二阶导数,或者差分等。看似高大上,其实无非就是左边像素值减去右边像素值、上面减去下面或者周围的减去中间的等,每个参与的像素乘以相应的系数。其中数学上的推导,还是看该篇文章吧。这里只给出具体算子,并且总结他们的优缺点。

下面的边缘提取图都是对该图的灰度图进行滤波的

一:Roberts算子

其kernel= [[1, 0],[0,-1]],或者[[0, 1],[-1, 0]].

    image_path = 'ori.jpg'
    img = cv2.imread(image_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img = img/255.0
    # Roberts
    fileter_data = np.array([[0, 1],
                            [-1, 0]])
    end_filter = cv2.filter2D(img, -1, fileter_data)
    end_filter = end_filter*255
    end_filter = cv2.convertScaleAbs(end_filter)
    cv2.imshow('name', end_filter)
    cv2.imwrite('filter_Roberts.jpg', end_filter)
    cv2.waitKey(10000)
    cv2.destroyAllWindows()

                                                                             filter_Roberts.jpg

由于ImageFilter模块不能用2*2的卷积核,这里采用了opencv的滤波方法。并且用浮点数进行了处理.因此卷积的时候,数值范围在(0,1)之间,最后乘以255后,利用cv2.convertScaleAbs()进行转换。

也可以直接利用uint8型:

    image_path = 'ori.jpg'
    img = cv2.imread(image_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # Roberts
    fileter_data = np.array([[0, 1],
                            [-1, 0]])
    end_filter = cv2.filter2D(img, cv2.CV_16S, fileter_data)
    end_filter = cv2.convertScaleAbs(end_filter)
    cv2.imshow('name', end_filter)
    cv2.imwrite('Roberts_255.jpg', end_filter)
    cv2.waitKey(10000)
    cv2.destroyAllWindows()

end_filter = cv2.filter2D(img, cv2.CV_16S, fileter_data),主要是这里的cv2.CV_16S参数,表示有符号16整型(S代表有符号整型,U无符号整型,F代表单精度浮点型),它的作用是在进行卷积前,将数值类型改变,因为在进行卷积的时候可能产生负数。而上个参开代码这里为-1,即默认和img类型时一样的数值类型。

二:Sobel算子

该算子分为纵向和横向两个模板:

经过该公式得到最后结果:

    image_path = 'ori.jpg'
    img = cv2.imread(image_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img = img/255.0
    # Sobel
    fileter_data_x = np.array([[-1, 0, 1],
                               [-2, 0, 2],
                               [-1, 0, 1]])

    fileter_data_y = np.array([[1, 2, 1],
                               [0, 0, 0],
                               [-1, -2, -1]])
    end_filter_x = cv2.filter2D(img, -1, fileter_data_x)
    end_filter_y = cv2.filter2D(img, -1, fileter_data_y)
    end_filter_xx = np.power(end_filter_x, 2)
    end_filter_yy = np.power(end_filter_y, 2)
    end_filter = np.power((end_filter_xx + end_filter_yy), 0.5)
    cv2.imshow('name', end_filter)
    cv2.imwrite('filter_Sobel.jpg', end_filter*255)
    cv2.waitKey(10000)
    cv2.destroyAllWindows()

                                                                   filter_Sobel

程序中一定要注意类型的转换,opencv中对浮点和uint8的对待方式不一样,浮点默认0-1,无符号整型为0-255

三 :Prewitt算子

Prewitt算子和sobel算子类似,其值只差两个数,但这两个数的差别,Prewitt却有滤波的作用。

采用Prewitt算子(Prewitt_x)与图像进行卷积时,卷积后的像素值的确定方案为:像素坐标右边纵向三个值的和减去左边纵向三个值的和。如果加上均值化,那么就是均值滤波,均值滤波有平滑图像的作用,因此就有去噪点的功能,相当于低通滤波。因此,相比Roberts算子,Prewitt算子对噪声有抑制作用。

    image_path = 'ori.jpg'
    img = cv2.imread(image_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img = img/255.0
    # Prewitt
    fileter_data_x = np.array([[-1, 0, 1],
                               [-1, 0, 1],
                               [-1, 0, 1]])

    fileter_data_y = np.array([[1, 1, 1],
                               [0, 0, 0],
                               [-1, -1, -1]])
    end_filter_x = cv2.filter2D(img, -1, fileter_data_x)
    end_filter_y = cv2.filter2D(img, -1, fileter_data_y)

    end_filter_xx = np.power(end_filter_x, 2)
    end_filter_yy = np.power(end_filter_y, 2)
    end_filter = np.power((end_filter_xx + end_filter_yy), 0.5)

    cv2.imshow('name', end_filter)
    cv2.imwrite('filter_Prewitt.jpg', cv2.convertScaleAbs(end_filter*255))
    cv2.waitKey(10000)
    cv2.destroyAllWindows()

                                                                                        filter_Prewitt.jpg

这两个算子都是通过X和Y两个方向共同确认的。因此不会有负数。

四:Laplacian算子

Laplacce算子是一种各向同性算子,二阶微分算子,它不能检测边缘的方向。Laplace算子对孤立象素的响应要比对边缘或线的响应要更强烈,因此对噪声特别敏感,一般情况下,使用Laplacian算子检测边缘之前需要先进行低通滤波。

                       扩展模板板

    image_path = 'ori.jpg'
    img = cv2.imread(image_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    #  Laplacian
    fileter_data = np.array([[0, 1, 0],
                             [1, -4, 1],
                             [0, 1, 0]])
    # fileter_data = np.array([[1, 1, 1],
    #                         [1, -8, 1],
    #                         [1, 1, 1]])

    end_filter = cv2.filter2D(img, cv2.CV_16S, fileter_data)
    end_filter = cv2.convertScaleAbs(end_filter)
    cv2.imshow('name', end_filter)
    cv2.imwrite('filter_Laplacian_plus.jpg', end_filter)
    cv2.waitKey(10000)
    cv2.destroyAllWindows()

                                                                                     filter_Laplacian

五:LOG(高斯-拉普拉斯)算子

针对拉普拉斯算子对噪声过于敏感,但是高斯模糊算子又具有平滑噪声的作用。因此LOG算子是高斯和拉普拉斯的双结合,即集平滑和边沿于一身的算子模型。具体推到以及来源可参考文章一文章二

常用模板为:

    image_path = 'ori.jpg'
    img = cv2.imread(image_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    #  log
    fileter_data = np.array([[0, 0, 1, 0, 0],
                             [0, 1, 2, 1, 0],
                             [1, 2, -16, 2, 1],
                             [0, 1, 2, 1, 0],
                             [0, 0, 1, 0, 0]])

    end_filter = cv2.filter2D(img, cv2.CV_16S, fileter_data)
    end_filter = cv2.convertScaleAbs(end_filter)

    cv2.imshow('name', end_filter)
    print(end_filter)
    cv2.imwrite('filter_log.jpg', end_filter)
    cv2.waitKey(10000)
    cv2.destroyAllWindows()

                                                                       filter_log

    上面的几种边缘提取算法,可还需要添加threshold阈值。按照理想的条件,提取后的边缘图的像素点,要么为0要么为1. 因此该阈值的就是区分像素应当置为0还是255。

但是,不设阈值的好处是通过亮度的程度能够感官上体会边缘锐的程度(锐度大小)。这里只是做一下提醒。

其实opencv已经提供了好多边缘提取的方法,只是为了更加详细的说明每一种滤波核的特点。采用opencv实现边缘提取的部分函数有:

cv2.Sobel()、 cv2.Laplacian()、cv2.Canny()等。

还可以使用scipy模块

import scipy.signal

    # mode = same 表示输出尺寸等于输入尺寸
    # boundary 表示采用对称边界条件处理图像边缘
scipy.signal.convolve2d(r, window, mode='same', boundary='symm')

锐化

上面提到的边缘算子对一种锐化方法特别有用,主要是利用图像边缘大部分是高频区域,将高频信息提取出来进行放大,然后叠加回去.从而实现锐化. 这种方法的应用如下:

首先使用某种方法对边缘信息进行提取. 

 我们提取的边缘信息以后,对这个边缘信息进行放大. 然后原图(模糊后的也可以)与处理后的边缘信息进行相加.这样就得出了锐化的结果.非常简单.

大体可以使用该公式表示.mx是窗口区域下的均值像素,也可以用高斯模糊求得.这里的G如果是常数的话,就属于线性增强.高频部分都被同等放大,有些地方可能出现增强过度的现象.

锐化自适应:

我们可以通过动态的调整C来实现自适应的方法

局部方差锐化自适应:

上式中σx(i,j)就是所谓的局部标准差(LSD),D是常数.

锐化自适应二 

   1、以5x5之类窗口,将输入图像网格化。
    2、通过梯度算法,计算出每个网格内平均梯度,存入掩码mask1中。
    3、使用高斯之类掩码,将mask1平滑。
    4、找出mask1上,梯度掩码最大值Vmax 和中间值Vmid = Vmax / 2。
    5、遍历整个mask1,通过公式:w=pow(abs(mask1[i,j]-Vmib)/Vmid, 0.6)计算出每个像素对应锐化强度权重w,存入Mask2。
    6、高斯模糊原图 - 原图,得到锐化掩码Mask3。
    7、Mask3 * Mask2的结果,叠加到原图上,得到锐化效果。
具体原理可以理解为:将图像分成了平坦区、细节区、和强细节区。对平坦区少做锐化,可以避免颗粒感之类噪声放大;对强锐化去少做锐化,可以避免图像出现严重的过锐,不自然现象。

方法三

对比度

在图像处理中还有一种概念是对比度,他和锐化同样可以使人感觉图像更加"鲜艳",但是对比度和锐化的概念并不一样.

对比度是让亮度地方更亮,而暗的地方更暗.因此,对像素值进行线性拉伸即可.或者使用一种伽马指数进行缩放.

如果原图上像素值最大的点值为max(可能离255还很远),像素值最小的点的值为min(可能离0值也很远),线性变换就是要把值域(min,max)拉伸到(0,255)。所以很简单了:y=k(x-min),其中k权且叫做拉伸系数吧,k=255/(max-min)。

文说道的max和min并不真的就是整张图上的最大值和最小值,实际上如果真的使用绝对的最大值和最小值的话,往往效果很差(值最大的那个点和值最小的那个点很有可能是坏点,或者是由噪声的影响)。所以需要在选取最大值和最小值中采取某种策略.其中一个方法是max取最大的5%的像素的值,min取最小的5%的像素的值(也未必一定是5%).

色彩增强

通过这篇,里面涉及到腾讯的色彩增强算法,大概就是通过提亮,对比度增强以及调整饱和度三种方法,这里只是通过他给出的公式,方便理解这三个操作的具体意义.

 第一步,亮度的改变,就是在每个像素值上乘以系数,比较好理解。

第二步的对比度增强,是每个像素减去所有像素的均值后乘以系数,然后在加上均值。从而实现整体图像的像素值拉伸的效果。

第三步,饱和度的改变。channel_mean,是每个通道总rgb三个像素点的均值。rgb的像素值拉伸的是相对该通道的三个像素点均值。

 

 

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值