Python版冈萨雷斯 V1.0 (二)

Python版冈萨雷斯 V1.0

第三章 亮度变换和空间滤波

系列 【第二章 基本原理】

使用OpenCV 库,OpenCV 是计算机视觉中经典的专用库,其支持多语言、跨平台,功能强大。

3.2亮度变换函数

图片是由像素矩阵构成的,对图片进行操作即为对图片的像素点矩阵进行操作。只要在这个像素点矩阵中找到这个像素点的位置,比如第x行,第y列,所以这个像素点在这个像素点矩阵中的位置就可以表示成(x,y),因为一个像素点的颜色由红、绿、蓝三个颜色变量表示(R,G,B),所以通过给这三个变量赋值,来改变这个像素点的颜色。

img1 = 255 - img        # 明暗反转 负片图像
img11 = cv.cvtColor(img, cv.COLOR_BGR2GRAY)   # 灰度图
img11 = 255 - img11     # 明暗反转 负片图像
cv.imshow('image1', img1)
cv.imshow('image11', img11)
img2 = img.copy()  # 采用按像素的方式改变图像对比度和亮度,公式如下 g(x) = αf(x) + β
rows,cols,channels = img.shape
a = 1.2  # 参数(a>0)表示放大的倍数(一般在0.0~3.0之间)
b = 50   # 参数一般称为偏置,用来调节亮度
for i in range(rows):
    for j in range(cols):
        for c in range(channels):
            color = img[i,j][c] * a + b
            if color > 255:           # 防止像素值越界(0~255)
                img2[i,j][c] = 255
            elif color < 0:           # 防止像素值越界(0~255)
                img2[i,j][c] = 0    
cv.imshow('image2',img2)
"""
函数详解:
addWeighted(InputArray_src1, 
            double_alpha, 
            InputArray_src2, 
            double_beta, 
            double_gamma, 
            OutputArray_dst, 
            int_dtype=-1
            );
"""
# 一共有七个参数:前4个是两张要合成的图片及它们所占比例,
#                             第5个double gamma起微调作用,
#                             第6个OutputArray dst是合成后的图片,
#                             第7个输出的图片的类型(可选参数,默认-1)
def Contrast_and_Brightness(alpha, beta, img):
    blank = np.zeros(img.shape, img.dtype)
    img3 = alpha * img + beta * blank
    img4 = cv.addWeighted(img, alpha, blank, 1-alpha, beta)  # 两个图片加成输出图片为:image = src1 * alpha + src2 * beta + gamma
    return img3, img4
img3, img4 = Contrast_and_Brightness(a, b, img)          
cv.imshow('image3',img3)
cv.imshow('image4',img4)

# 对数和对比度拉伸变换
对数与对比度拉伸变换是进行动态范围处理的基本工具。对数变换通过如下表达式实现:
g = c * log(1 + double(f))
其中,c是一个常数。对数变换的一项主要应用是压缩动态范围,被广泛地应用于频谱图像的显示中。一个典型的应用是傅立叶频谱,其动态范围可能宽达0~10**6直接显示频谱时,图像显示设备的动态范围往往不能满足要求,从而丢失大量的暗部细节;而在使用对数变换之后,图像的动态范围被合理地非线性压缩,从而可以清晰地显示。

#绘制曲线
def log_plot(c):
    x = np.arange(0, 256, 0.01)
    y = c * np.log(1 + x)
    plt.plot(x, y, 'r', linewidth=1)
    plt.rcParams['font.sans-serif']=['SimHei'] #正常显示中文标签
    plt.title(u'对数变换函数')
    plt.xlim(0, 255), plt.ylim(0, 255)
    plt.show()
#对数变换
def log(c, img):
    output = c * np.log(1.0 + img)
    output = np.uint8(output + 0.5)
    return output
#绘制对数变换曲线
log_plot(36)
#图像灰度对数变换
log_img = log(36, img)
#显示图像
cv.imshow('image', img)
cv.imshow('log_img', log_img)

伽马(幂律)变换,是另一种常用的灰度非线性变换。图像灰度的伽马变换如公式所示:
g(x) = c * f(x)** γ
当 γ > 1 时,拉伸图像中灰度级较高的区域,压缩灰度级较低的部分。
当 γ < 1 时,拉伸图像中灰度级较低的区域,压缩灰度级较高的部分。
当 γ = 1 时,该灰度变换是线性的,此时通过线性方式改变原图像。

#绘制曲线
def gamma_plot(c, v):
    x = np.arange(0, 256, 0.01)
    y = c * x ** v
    plt.plot(x, y, 'r', linewidth=1)
    plt.rcParams['font.sans-serif']=['SimHei']   #正常显示中文标签
    plt.title(u'伽马变换函数')
    plt.xlim([0, 255]), plt.ylim([0, 255])
    plt.show()
#伽玛变换
def gamma(img, c, v):
    lut = np.zeros(256, dtype=np.float32)
    for i in range(256):
        lut[i] = c * i ** v
    output_img = cv.LUT(img, lut)   #像素灰度值的映射
    output_img = np.uint8(output_img + 0.5)  
    return output_img
#绘制伽玛变换曲线
# gamma_plot(0.000005, 0.5)
#图像灰度幂律变换
gamma_img = gamma(img, 0.000005, 3.0)
#显示图像
cv.imshow('gamma_img', gamma_img)

3.3直方图处理和函数绘图

3.3.1生成并绘制图像的直方图
一幅数字图像在范围[0,G]内共有L个灰度级,其直方图定义为离散函数:
h(k) = 
其中,k是区间[0,G]内的第k级亮度,k是灰度级为k的图像中的像素数。对于 unit 8 类图像,G的值为255;对于 unit 16 类图像,G的值为65535;对于double类图像,G的值为1.0。

# 使用OpenCV统计绘制直方图
'''
    image:输入图像,传入时用中括号[]括起来
    channels:传入图像的通道,如果是灰度图像,只有一个通道,值为0,如果是彩色图像(有3个通道),值为0,1,2,中选择一个,对应着BGR各个通道,值用[]传入。
    mask:掩膜图像。如果统计整幅图,设置为none。如果要统计部分图的直方图,构造相应的mask来计算。
    histSize:灰度级的个数,需要中括号,比如[256]
    ranges:像素值的范围,通常[0,256]
'''
histb = cv.calcHist([img], [0], None, [256], [0,255])
histg = cv.calcHist([img], [1], None, [256], [0,255])
histr = cv.calcHist([img], [2], None, [256], [0,255])
# print('hist:', hist)
# print('hist.shape:', hist.shape)
plt.plot(histb, color='b')
plt.plot(histg, color='g')
plt.plot(histr, color='r')

plt.hist(img.ravel(), 256, [0,256])
plt.show()
# 使用numpy
import numppy as np

hist1, bins = np.histogram(img.ravel(), 256, [0,256])
hist2 = np.bincount(img.ravel(), minlength=256) #速度较上函数快十倍左右
plt.plot(hist1, color='g')
# plt.plot(hist2, color='r')
plt.show()

3.3.2直方图均衡化
假设灰度级为归一化至范围[0,1]内的连续量,并令r(r)表示某给定图像中的灰度级的概率密度函数(PDF),其下标用来区分输入图像和输出图像的PDF。对输入灰度级执行如下变换,得到输出灰度级s:
在这里插入图片描述
其中是积分的哑变量。可以看出输出灰度级的概率密度函数是均匀的,即:
Alt在这里插入图片描述
该变换函数是一个累积分布函数(PDF)。

img_res = cv.equalizeHist(img_GRAY)   # 全局均衡化
cv.imshow('img_res', img_res)
clahe = cv.createCLAHE(clipLimit=2, tileGridSize=(10,10))  # 局部均衡化,(10,10)
img_res1 = clahe.apply(img_GRAY)
cv.imshow('img_res1', img_res1)
(b, g, r) = cv.split(img_BGR)  # 彩色图像均衡化,需要分解通道,对每一个通道均衡化
bH = cv.equalizeHist(b)
gH = cv.equalizeHist(g)
rH = cv.equalizeHist(r)
img_res2 = cv.merge((bH, gH, rH))  # 合并每一个通道
cv.imshow("img_res2", img_res2)

3.4 空间滤波

3.4.1线性空间滤波
#2D卷积
与一维信号一样,也可以对 2D 图像实施低通滤波(LPF),高通滤波(HPF)等。LPF 去除噪音,模糊图像。HPF 检测图像边缘。

OpenCV 提供的函数 cv.filter2D() 可以对一幅图像进行卷积操作。下面是一个 5x5 的平均滤波器核:
5x5 的平均滤波器核
将核放在图像的一个像素 P 上,求与核对应的图像上 25(5x5)个像素的和,取平均数,用这个平均数替代像素 P 的值。重复以上操作直到将图像的每一个像素值都更新一遍。

kernel = np.ones((5,5), np.float32)/25 
# cv.Filter2D(src, dst, kernel, anchor=(-1, -1)) 
# ddepth –desired depth of the destination image; 
# if it is negative, it will be the same as src.depth(); 
# the following combinations of src.depth() and ddepth are supported: 
# src.depth() = CV_8U, ddepth = -1/CV_16S/CV_32F/CV_64F 
# src.depth() = CV_16U/CV_16S, ddepth = -1/CV_32F/CV_64F 
# src.depth() = CV_32F, ddepth = -1/CV_32F/CV_64F 
# src.depth() = CV_64F, ddepth = -1/CV_64F 
# when ddepth = -1, the output image will have the same depth as the source. 
dst = cv.filter2D(img, -1, kernel) 

使用低通滤波器可以达到图像模糊的目的。这对与去除噪音很有帮助。其实就是去除图像中的高频成分(比如:噪音,边界)。所以边界也会被模糊一点。

#均值滤波
这是由一个归一化卷积框完成的。只是用卷积框覆盖区域所有像素的平均值来代替中心元素。可以使用函数 cv.blur() 和 cv.boxFilter() 来完这个任务。
3*3的平均滤波器核

# 均值滤波
blur = cv.blur(result, (5,5))
# 方框滤波
boxF = cv.boxFilter(result, -1, (5,5), normalize = 0)   # 1 归一化与均值滤波相同

线性空间滤波的机理
线性空间滤波的机理。放大图显示了大小3*3的掩膜以及掩膜正下方的相应图像邻域。

#高斯滤波
现在把卷积核换成高斯核(方框不变,原来每个方框的值是相等的,现在里面的值是高斯分布的,方框中心的值最大,其余方框根据距离中心元素的距离递减,构成一个高斯小山丘。原来求平均数现在变成求加权平均数,全是方框里的值)。实现的函数是 cv.GaussianBlur()。需指定高斯核的宽和高(必须是奇数)。以及高斯函数沿 X,Y 方向的标准差。如果只指定了 X 方向的的标准差,Y 方向也会取相同值。如果两个标准差都是 0,那么函数会根据核函数的大小自己计算。高斯滤波可以有效的从图像中去除高斯噪音。

# 0 是指根据窗口大小(5,5)来计算高斯函数标准差 
blur = cv.GaussianBlur(img, (5,5), 0)

3.4.2非线性空间滤波
#中值滤波
顾名思义就是用与卷积框对应像素的中值来替代中心像素的值。这个滤波器经常用来去除椒盐噪声。前面的滤波器都是用计算得到的一个新值来取代中心像素的值,而中值滤波是用中心像素周围(也可以使他本身)的值来取代他。能有效的去除噪声。卷积核的大小也应该是一个奇数。

median = cv.medianBlur(img, 5)

#双边滤波
函数 cv.bilateralFilter() 能在保持边界清晰的情况下有效的去除噪音。但是这种操作与其他滤波器相比会比较慢。高斯滤波器是求中心点邻近区域像素的高斯加权平均值。这种高斯滤波器只考虑像素之间的空间关系,而不会考虑像素值之间的关系(像素的相似度)。所以这种方法不会考虑一个像素是否位于边界,因此边界也会别模糊掉。

双边滤波在同时使用空间高斯权重和灰度值相似性高斯权重。空间高斯函数确保只有邻近区域的像素对中心点有影响,灰度值相似性高斯函数确保只有与中心像素灰度值相近的才会被用来做模糊运算。所以这种方法会确保边界不会被模糊掉,因为边界处的灰度值变化比较大。

# cv.bilateralFilter(src, d, sigmaColor, sigmaSpace)
# d – Diameter of each pixel neighborhood that is used during filtering.
# If it is non-positive, it is computed from sigmaSpace
# 9 邻域直径,两个 75 分别是空间高斯函数标准差,灰度值相似性高斯函数标准差
blur = cv.bilateralFilter(img, 9, 75, 75)

附: 使用python读取两层嵌套文件夹中的图像数据集

文件目录结构如下:
文件目录结构
第一种方法:使用 os.walk() 函数

概述

os.walk() 方法用于通过在目录树中游走输出在目录中的文件名,向上或者向下。 os.walk()方法是一个简单易用的文件、目录遍历器,可以帮助我们高效的处理文件、目录方面的事情。 在Unix,Windows中有效。

语法

walk()方法语法格式如下:

os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]])

参数

top -- 是你所要遍历的目录的地址, 返回的是一个三元组(root,dirs,files)。
    root 所指的是当前正在遍历的这个文件夹的本身的地址
    dirs 是一个 list ,内容是该文件夹中所有的目录的名字(不包括子目录)
    files 同样是 list , 内容是该文件夹中所有的文件(不包括子目录)

topdown --可选,为 True,则优先遍历 top 目录,否则优先遍历 top 的子目录(默认为开启)。如果 topdown 参数为 True,walk 会遍历top文件夹,与top 文件夹中每一个子目录。

onerror -- 可选,需要一个 callable 对象,当 walk 需要异常时,会调用。

followlinks -- 可选,如果为 True,则会遍历目录下的快捷方式(linux 下是软连接 symbolic link )实际所指的目录(默认关闭),如果为 False,则优先遍历 top 的子目录。

返回值

该方法没有返回值。

实例

以下实例演示了 walk() 方法的使用:

import os
train_images = []
test_images = []
#遍历文件夹   
def iter_files(rootDir):
    #遍历根目录
    i = 1
    for root, dirs, files in os.walk(rootDir):
        print(i)    # 统计循环次数
        print('root:', root)
        print('dirs:', dirs)
        print('files:', files)
        if root.endswith('train'):
            for file in files:
                file_path = os.path.join(root, file)
                # print('file_path:', file_path)
                image = cv.imread(file_path, 1)
                train_images.append(image)
            print('train load success!')
        if root.endswith('test'):
            for file in files:
                file_path = os.path.join(root, file)
                # print('file_path:', file_path)
                image = cv.imread(file_path, 1)
                test_images.append(image)
            print('test load success!')
        i += 1

调用 iter_files() 函数输出结果如下:

1
root: E:/101_ObjectCategories/1
dirs: ['test', 'train']
files: ['test.txt', 'train.txt']
2
root: E:/101_ObjectCategories/1\test
dirs: []
files: ['image_0002.jpg', 'image_0003.jpg']
test load success!
3
root: E:/101_ObjectCategories/1\train
dirs: []
files: ['image_0001.jpg', 'image_0002.jpg']
train load success!

第二种方法使用 os.listdir() 函数:

概述

os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。 它不包括 . 和 … 即使它在文件夹中。 只支持在Unix, Windows 下使用。

语法

listdir() 方法语法格式如下:

os.listdir(path)

参数

path -- 需要列出的目录路径

返回值

返回指定路径下的文件和文件夹列表。

实例

以下实例演示了 listdir() 方法的使用:

def iter_file(rootDir):
    listdir = os.listdir(rootDir + '/train')
    print('listdir:', listdir)
    for file in listdir:
        image = cv.imread(rootDir + '/' + file, 1)
        train_images.append(image)
    print('train load success!')
    listdir = os.listdir(rootDir + '/test')
    print('listdir:', listdir)
    for file in listdir:
        image = cv.imread(rootDir + '/' + file, 1)
        test_images.append(image)
    print('test load success!')

调用 iter_file() 函数输出结果如下:

listdir: ['image_0001.jpg', 'image_0002.jpg']
train load success!
listdir: ['image_0002.jpg', 'image_0003.jpg']
test load success!

第四章待更。。。

猜你喜欢

转载自blog.csdn.net/qq_35200351/article/details/106952998