精华内容
参与话题
问答
  • 前面一篇文章我讲解了Python图像量化、采样处理及图像金字塔。本文主要讲解图像傅里叶变换的相关内容,在数字图像处理中,有两个经典的变换被广泛应用——傅里叶变换和霍夫变换。其中,傅里叶变换主要是将时间域上的...

    该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门、OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子、图像增强技术、图像分割等,后期结合深度学习研究图像识别、图像分类应用。希望文章对您有所帮助,如果有不足之处,还请海涵~

    该系列在github所有源代码:https://github.com/eastmountyxz/ImageProcessing-Python
    PS:请求帮忙点个Star,哈哈,第一次使用Github,以后会分享更多代码,一起加油。

    同时推荐作者的C++图像系列知识:
    [数字图像处理] 一.MFC详解显示BMP格式图片
    [数字图像处理] 二.MFC单文档分割窗口显示图片
    [数字图像处理] 三.MFC实现图像灰度、采样和量化功能详解
    [数字图像处理] 四.MFC对话框绘制灰度直方图
    [数字图像处理] 五.MFC图像点运算之灰度线性变化、灰度非线性变化、阈值化和均衡化处理详解
    [数字图像处理] 六.MFC空间几何变换之图像平移、镜像、旋转、缩放详解
    [数字图像处理] 七.MFC图像增强之图像普通平滑、高斯平滑、Laplacian、Sobel、Prewitt锐化详解

    前文参考:
    [Python图像处理] 一.图像处理基础知识及OpenCV入门函数
    [Python图像处理] 二.OpenCV+Numpy库读取与修改像素
    [Python图像处理] 三.获取图像属性、兴趣ROI区域及通道处理
    [Python图像处理] 四.图像平滑之均值滤波、方框滤波、高斯滤波及中值滤波
    [Python图像处理] 五.图像融合、加法运算及图像类型转换
    [Python图像处理] 六.图像缩放、图像旋转、图像翻转与图像平移
    [Python图像处理] 七.图像阈值化处理及算法对比
    [Python图像处理] 八.图像腐蚀与图像膨胀
    [Python图像处理] 九.形态学之图像开运算、闭运算、梯度运算
    [Python图像处理] 十.形态学之图像顶帽运算和黑帽运算
    [Python图像处理] 十一.灰度直方图概念及OpenCV绘制直方图
    [Python图像处理] 十二.图像几何变换之图像仿射变换、图像透视变换和图像校正
    [Python图像处理] 十三.基于灰度三维图的图像顶帽运算和黑帽运算
    [Python图像处理] 十四.基于OpenCV和像素处理的图像灰度化处理
    [Python图像处理] 十五.图像的灰度线性变换
    [Python图像处理] 十六.图像的灰度非线性变换之对数变换、伽马变换
    [Python图像处理] 十七.图像锐化与边缘检测之Roberts算子、Prewitt算子、Sobel算子和Laplacian算子
    [Python图像处理] 十八.图像锐化与边缘检测之Scharr算子、Canny算子和LOG算子
    [Python图像处理] 十九.图像分割之基于K-Means聚类的区域分割
    [Python图像处理] 二十.图像量化处理和采样处理及局部马赛克特效
    [Python图像处理] 二十一.图像金字塔之图像向下取样和向上取样

    前面一篇文章我讲解了Python图像量化、采样处理及图像金字塔。本文主要讲解图像傅里叶变换的相关内容,在数字图像处理中,有两个经典的变换被广泛应用——傅里叶变换和霍夫变换。其中,傅里叶变换主要是将时间域上的信号转变为频率域上的信号,用来进行图像除噪、图像增强等处理。基础性文章,希望对你有所帮助。同时,该部分知识均为杨秀璋查阅资料撰写,转载请署名CSDN+杨秀璋及原地址出处,谢谢!!

    1.图像傅里叶变换
    2.Numpy实现傅里叶变换
    3.Numpy实现傅里叶逆变换
    4.OpenCV实现傅里叶变换
    5.OpenCV实现傅里叶逆变换


    PS:文章参考自己以前系列图像处理文章及OpenCV库函数,同时参考如下文献:
    《数字图像处理》(第3版),冈萨雷斯著,阮秋琦译,电子工业出版社,2013年.
    《数字图像处理学》(第3版),阮秋琦,电子工业出版社,2008年,北京.
    《OpenCV3编程入门》,毛星云,冷雪飞,电子工业出版社,2015,北京.
    百度百科-傅里叶变换
    网易云课堂-高登教育 Python+OpenCV图像处理
    安安zoe-图像的傅里叶变换
    daduzimama-图像的傅里叶变换的迷思----频谱居中
    tenderwx-数字图像处理-傅里叶变换在图像处理中的应用
    小小猫钓小小鱼-深入浅出的讲解傅里叶变换(真正的通俗易懂)


    一.图像傅里叶变换原理

    傅里叶变换(Fourier Transform,简称FT)常用于数字信号处理,它的目的是将时间域上的信号转变为频率域上的信号。随着域的不同,对同一个事物的了解角度也随之改变,因此在时域中某些不好处理的地方,在频域就可以较为简单的处理。同时,可以从频域里发现一些原先不易察觉的特征。傅里叶定理指出“任何连续周期信号都可以表示成(或者无限逼近)一系列正弦信号的叠加。”

    下面引用李老师 “Python+OpenCV图像处理” 中的一个案例,非常推荐同学们去购买学习。如下图所示,他将某饮料的制作过程的时域角度转换为频域角度。

    绘制对应的时间图和频率图如下所示:

    傅里叶公式如下,其中w表示频率,t表示时间,为复变函数。它将时间域的函数表示为频率域的函数f(t)的积分。

    傅里叶变换认为一个周期函数(信号)包含多个频率分量,任意函数(信号)f(t)可通过多个周期函数(或基函数)相加合成。从物理角度理解,傅里叶变换是以一组特殊的函数(三角函数)为正交基,对原函数进行线性变换,物理意义便是原函数在各组基函数的投影。如下图所示,它是由三条正弦曲线组合成。

    傅里叶变换可以应用于图像处理中,经过对图像进行变换得到其频谱图。从谱频图里频率高低来表征图像中灰度变化剧烈程度。图像中的边缘信号和噪声信号往往是高频信号,而图像变化频繁的图像轮廓及背景等信号往往是低频信号。这时可以有针对性的对图像进行相关操作,例如图像除噪、图像增强和锐化等。

    二维图像的傅里叶变换可以用以下数学公式(15-3)表达,其中f是空间域(Spatial Domain))值,F是频域(Frequency Domain)值

    对上面的傅里叶变换有了大致的了解之后,下面通过Numpy和OpenCV分别讲解图像傅里叶变换的算法及操作代码。


    二.Numpy实现傅里叶变换

    Numpy中的 FFT包提供了函数 np.fft.fft2()可以对信号进行快速傅里叶变换,其函数原型如下所示,该输出结果是一个复数数组(Complex Ndarry)。

    fft2(a, s=None, axes=(-2, -1), norm=None)

    • a表示输入图像,阵列状的复杂数组
    • s表示整数序列,可以决定输出数组的大小。输出可选形状(每个转换轴的长度),其中s[0]表示轴0,s[1]表示轴1。对应fit(x,n)函数中的n,沿着每个轴,如果给定的形状小于输入形状,则将剪切输入。如果大于则输入将用零填充。如果未给定’s’,则使用沿’axles’指定的轴的输入形状
    • axes表示整数序列,用于计算FFT的可选轴。如果未给出,则使用最后两个轴。“axes”中的重复索引表示对该轴执行多次转换,一个元素序列意味着执行一维FFT
    • norm包括None和ortho两个选项,规范化模式(请参见numpy.fft)。默认值为无

    Numpy中的fft模块有很多函数,相关函数如下:

    #计算一维傅里叶变换
    numpy.fft.fft(a, n=None, axis=-1, norm=None)
    #计算二维的傅里叶变换
    numpy.fft.fft2(a, n=None, axis=-1, norm=None)
    #计算n维的傅里叶变换
    numpy.fft.fftn()
    #计算n维实数的傅里叶变换
    numpy.fft.rfftn()
    #返回傅里叶变换的采样频率
    numpy.fft.fftfreq()
    #将FFT输出中的直流分量移动到频谱中央
    numpy.fft.shift()

    下面的代码是通过Numpy库实现傅里叶变换,调用np.fft.fft2()快速傅里叶变换得到频率分布,接着调用np.fft.fftshift()函数将中心位置转移至中间,最终通过Matplotlib显示效果图。

    # -*- coding: utf-8 -*-
    import cv2 as cv
    import numpy as np
    from matplotlib import pyplot as plt
    
    #读取图像
    img = cv.imread('test.png', 0)
    
    #快速傅里叶变换算法得到频率分布
    f = np.fft.fft2(img)
    
    #默认结果中心点位置是在左上角,
    #调用fftshift()函数转移到中间位置
    fshift = np.fft.fftshift(f)       
    
    #fft结果是复数, 其绝对值结果是振幅
    fimg = np.log(np.abs(fshift))
    
    #展示结果
    plt.subplot(121), plt.imshow(img, 'gray'), plt.title('Original Fourier')
    plt.axis('off')
    plt.subplot(122), plt.imshow(fimg, 'gray'), plt.title('Fourier Fourier')
    plt.axis('off')
    plt.show()
    

    输出结果如图15-2所示,左边为原始图像,右边为频率分布图谱,其中越靠近中心位置频率越低,越亮(灰度值越高)的位置代表该频率的信号振幅越大。


    三.Numpy实现傅里叶逆变换

    下面介绍Numpy实现傅里叶逆变换,它是傅里叶变换的逆操作,将频谱图像转换为原始图像的过程。通过傅里叶变换将转换为频谱图,并对高频(边界)和低频(细节)部分进行处理,接着需要通过傅里叶逆变换恢复为原始效果图。频域上对图像的处理会反映在逆变换图像上,从而更好地进行图像处理。

    图像傅里叶变化主要使用的函数如下所示:

    #实现图像逆傅里叶变换,返回一个复数数组
    numpy.fft.ifft2(a, n=None, axis=-1, norm=None)
    #fftshit()函数的逆函数,它将频谱图像的中心低频部分移动至左上角
    numpy.fft.fftshift()
    #将复数转换为0至255范围
    iimg = numpy.abs(逆傅里叶变换结果)

    下面的代码分别实现了傅里叶变换和傅里叶逆变换。

    # -*- coding: utf-8 -*-
    import cv2 as cv
    import numpy as np
    from matplotlib import pyplot as plt
    
    #读取图像
    img = cv.imread('Lena.png', 0)
    
    #傅里叶变换
    f = np.fft.fft2(img)
    fshift = np.fft.fftshift(f)
    res = np.log(np.abs(fshift))
    
    #傅里叶逆变换
    ishift = np.fft.ifftshift(fshift)
    iimg = np.fft.ifft2(ishift)
    iimg = np.abs(iimg)
    
    #展示结果
    plt.subplot(131), plt.imshow(img, 'gray'), plt.title('Original Image')
    plt.axis('off')
    plt.subplot(132), plt.imshow(res, 'gray'), plt.title('Fourier Image')
    plt.axis('off')
    plt.subplot(133), plt.imshow(iimg, 'gray'), plt.title('Inverse Fourier Image')
    plt.axis('off')
    plt.show()
    

    输出结果如图15-4所示,从左至右分别为原始图像、频谱图像、逆傅里叶变换转换图像。


    四.OpenCV实现傅里叶变换

    OpenCV 中相应的函数是cv2.dft()和用Numpy输出的结果一样,但是是双通道的。第一个通道是结果的实数部分,第二个通道是结果的虚数部分,并且输入图像要首先转换成 np.float32 格式。其函数原型如下所示:

    dst = cv2.dft(src, dst=None, flags=None, nonzeroRows=None)

    • src表示输入图像,需要通过np.float32转换格式
    • dst表示输出图像,包括输出大小和尺寸
    • flags表示转换标记,其中DFT _INVERSE执行反向一维或二维转换,而不是默认的正向转换;DFT _SCALE表示缩放结果,由阵列元素的数量除以它;DFT _ROWS执行正向或反向变换输入矩阵的每个单独的行,该标志可以同时转换多个矢量,并可用于减少开销以执行3D和更高维度的转换等;DFT _COMPLEX_OUTPUT执行1D或2D实数组的正向转换,这是最快的选择,默认功能;DFT _REAL_OUTPUT执行一维或二维复数阵列的逆变换,结果通常是相同大小的复数数组,但如果输入数组具有共轭复数对称性,则输出为真实数组
    • nonzeroRows表示当参数不为零时,函数假定只有nonzeroRows输入数组的第一行(未设置)或者只有输出数组的第一个(设置)包含非零,因此函数可以处理其余的行更有效率,并节省一些时间;这种技术对计算阵列互相关或使用DFT卷积非常有用

    注意,由于输出的频谱结果是一个复数,需要调用cv2.magnitude()函数将傅里叶变换的双通道结果转换为0到255的范围。其函数原型如下:

    cv2.magnitude(x, y)

    • x表示浮点型X坐标值,即实部
    • y表示浮点型Y坐标值,即虚部
      最终输出结果为幅值,即:

    完整代码如下所示:

    # -*- coding: utf-8 -*-
    import numpy as np
    import cv2
    from matplotlib import pyplot as plt
    
    #读取图像
    img = cv2.imread('Lena.png', 0)
    
    #傅里叶变换
    dft = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)
    
    #将频谱低频从左上角移动至中心位置
    dft_shift = np.fft.fftshift(dft)
    
    #频谱图像双通道复数转换为0-255区间
    result = 20*np.log(cv2.magnitude(dft_shift[:,:,0], dft_shift[:,:,1]))
    
    #显示图像
    plt.subplot(121), plt.imshow(img, cmap = 'gray')
    plt.title('Input Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(result, cmap = 'gray')
    plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
    plt.show()
    

    输出结果如图15-5所示,左边为原始“Lena”图,右边为转换后的频谱图像,并且保证低频位于中心位置。


    五.OpenCV实现傅里叶逆变换

    在OpenCV 中,通过函数cv2.idft()实现傅里叶逆变换,其返回结果取决于原始图像的类型和大小,原始图像可以为实数或复数。其函数原型如下所示:

    dst = cv2.idft(src[, dst[, flags[, nonzeroRows]]])

    • src表示输入图像,包括实数或复数
    • dst表示输出图像
    • flags表示转换标记
    • nonzeroRows表示要处理的dst行数,其余行的内容未定义(请参阅dft描述中的卷积示例)

    完整代码如下所示:

    # -*- coding: utf-8 -*-
    import numpy as np
    import cv2
    from matplotlib import pyplot as plt
    
    #读取图像
    img = cv2.imread('Lena.png', 0)
    
    #傅里叶变换
    dft = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)
    dftshift = np.fft.fftshift(dft)
    res1= 20*np.log(cv2.magnitude(dftshift[:,:,0], dftshift[:,:,1]))
    
    #傅里叶逆变换
    ishift = np.fft.ifftshift(dftshift)
    iimg = cv2.idft(ishift)
    res2 = cv2.magnitude(iimg[:,:,0], iimg[:,:,1])
    
    #显示图像
    plt.subplot(131), plt.imshow(img, 'gray'), plt.title('Original Image')
    plt.axis('off')
    plt.subplot(132), plt.imshow(res1, 'gray'), plt.title('Fourier Image')
    plt.axis('off')
    plt.subplot(133), plt.imshow(res2, 'gray'), plt.title('Inverse Fourier Image')
    plt.axis('off')
    plt.show()
    

    输出结果如图15-6所示,第一幅图为原始“Lena”图,第二幅图为傅里叶变换后的频谱图像,第三幅图为傅里叶逆变换,频谱图像转换为原始图像的过程。


    六.总结

    傅里叶变换的目的并不是为了观察图像的频率分布(至少不是最终目的),更多情况下是为了对频率进行过滤,通过修改频率以达到图像增强、图像去噪、边缘检测、特征提取、压缩加密等目的。下一篇文章,作者将结合傅里叶变换和傅里叶逆变换讲解它的应用。

    时也,命也。
    英语低分数线一分,些许遗憾,但不气馁,更加努力。雄关漫道真如铁,而今迈过从头越,从头越。苍山如海,残阳如血。感谢一路陪伴的人和自己。

    无论成败,那段拼搏的日子都很美。结果只会让我更加努力,学好英语。下半年沉下心来好好做科研写文章,西藏之行,课程分享。同时,明天的博士考试加油,虽然裸泳,但也加油!还有春季招考开始准备。

    最后补充马刺小石匠精神,当一切都看起来无济于事的时候,我去看一个石匠敲石头.他一连敲了100次,石头仍然纹丝不动。但他敲第101次的时候,石头裂为两半。可我知道,让石头裂开的不是那最后一击,而是前面的一百次敲击的结果。人生路漫漫,不可能一路一帆风顺,暂时的不顺只是磨练自己的必经之路,夜最深的时候也是距黎明最近的时刻,经历过漫漫长夜的打磨,你自身会更加强大。

    最后希望这篇基础性文章对您有所帮助,如果有错误或不足之处,请海涵!

    (By:Eastmount 2019-04-23 周二下午6点写于花溪 https://blog.csdn.net/Eastmount )


    2020年8月18新开的“娜璋AI安全之家”,主要围绕Python大数据分析、网络空间安全、人工智能、Web渗透及攻防技术进行讲解,同时分享CCF、SCI、南核北核论文的算法实现。娜璋之家会更加系统,并重构作者的所有文章,从零讲解Python和安全,写了近十年文章,真心想把自己所学所感所做分享出来,还请各位多多指教,真诚邀请您的关注!谢谢。

    展开全文
  • 超全Python图像处理讲解(多图预警)

    万次阅读 多人点赞 2020-01-01 17:01:08
    文章目录Pillow模块讲解一、Image模块1.1 、打开图片和显示图片1.2、创建一个简单的图像1.3、图像混合(1)透明度混合(2)遮罩混合1.4、图像缩放(1)按像素缩放(2)按尺寸缩放1.5、图像的剪切与粘贴(1)图像粘贴...

    Pillow模块讲解

    一、Image模块

    1.1 、打开图片和显示图片

    对图片的处理最基础的操作就是打开这张图片,我们可以使用Image模块中的open(fp, mode)方法,来打开图片。open方法接收两个参数,第一个是文件路径,第二个是模式。主要的模式如下:

    mode(模式) bands(通道) 说明
    “1” 1 数字1,表示黑白二值图片,每个像素用0或1共1位二进制码表示
    “L” 1 灰度图
    “P” 1 索引图
    “RGB” 3 24位真彩图
    “RGBA” 4 “RGB”+透明通道
    “CMYK” 4 印刷模式图像

    光理论是不够的,在此送大家一套2020最新Python全栈项目视频教程,点击此处 进来获取 跟着练习下,希望大家一起进步哦!
    更多的模式也就不说了,关于模式的模式的详细介绍我也不知道。这个open方法返回一个Image对象,mode也不是必须参数。打开图片代码如下:

    from PIL import Image
    # 打开图片
    im = Image.open('test.jpg')
    # 显示图片
    im.show()
    

    当然显示图片不是我们的重点,我们获取Image对象之后,就可以获取它的一些信息了。

    print('图像的格式:', im.format)
    print('图像的大小:', im.size)
    print('图像的宽度:', im.width)
    print('图像的高度:', im.height)
    # 传入坐标的元组
    print('获取某个像素点的颜色值:', im.getpixel(100, 100))
    

    在我的环境中运行结果如下:

    图像的格式: JPEG
    图像的大小: (3968, 2976)
    图像的宽度: 3968
    图像的高度: 2976
    获取某个像素点的颜色值: (198, 180, 132)
    

    1.2、创建一个简单的图像

    在Image模块中,提供了创建图像的方法。主要是通过**Image.new(mode, size, color)**实现,该方法传入三个参数:

    • mode:图像的创建模式
    • size:图像的大小
    • color:图像的颜色

    用该方法可以创建一个简单的图像,之后我们可以通过save方法将图像保存:

    from PIL import Image
    # 创建一个简单的图像
    im = Image.new('RGB', (100, 100), 'red')
    # 保存这个图像
    im.save('red.png')
    

    生成图片如下:

    1.3、图像混合

    (1)透明度混合

    透明度混合主要是使用**Image中的blend(im1, im2, alpha)**方法,对该方法的解释如下:

    • im1:Image对象,在混合的过程中,透明度设置为(1-apha)
    • im2:Image对象,在混合的过程中,透明度设置为(apha)
    • alpha:透明度,取值是0-1。当透明度为0是,显示im1对象;当透明度为1时,显示im2对象

    注意:im1和im2的大小必须一样,且mode都为RGB

    代码实现如下:

    from PIL import Image
    
    # 打开im1
    im1 = Image.open('pic.jpg').convert(mode='RGB')
    # 创建一个和im1大小一样的图像
    im2 = Image.new('RGB', im1.size, 'red')
    # 混合图片,并显示
    Image.blend(im1, im2, 0.5).show()
    

    下面为原图和混合图的对比:

    不得不说,我家艾斯真滴帅。

    (2)遮罩混合

    接下来就是很迷的时刻了,我们可以通过Image.composite(im1, im2, mask)方法实现遮罩混合。三个参数都是Image对象,该方法的作用就是使用mask来混合im1和im2。我是听不懂,你们能听懂最好给我讲一下。具体实现如下:

    # 这句代码写了好多遍,我真不想写了
    from PIL import Image
    # 打开图像1
    im1 = Image.open('pic1.jpg')
    # 打开图像2
    im2 = Image.open('pic2.jpg')
    # 重新设置im2的大小
    im2.resize(im1.size)
    # 将图像2的三个色道分离,其中r、g、b都为Image对象
    r, g, b = im2.split()
    # 遮罩混合
    Image.composite(im1, im2, b).show()
    

    注意:im1、im2和mask的大小必须一样

    im1、im2和遮罩混合效果对比如下:

    依旧是我帅气的艾斯。

    1.4、图像缩放

    (1)按像素缩放

    按像素缩放通过Image.eval(im1, fun)方法实现,其中im1为我们老生常谈的Image对象了;第二个为一个方法(函数),该函数传入一个参数,即像素点。该函数会对图片中每个像素点进行函数内的操作。下面我们对来简单使用一下这个方法:

    from PIL import Image
    # 打开一张图像
    im = Image.open('抠鼻屎.jpg')
    # 对该图像每个像素点进行*2处理
    Image.eval(im, lambda x:x*2).show()
    

    这里我使用的lambda表达式,当然一般也都是用lambda表达式,不过你也可以像下面这样写:

    # 定义一个方法
    def func(x):
        return x*2
    # 对图像im每个像素点进行func中的操作,其中func不能加()
    Image.eval(im, func)
    

    效果图如下:

    细心的读者应该可以发现,这个抠鼻屎的图片和笔者头像并不完全一样。在血色方面,笔者的头像确实要差几分。

    注意:笔者在日常生活中可不是天天在大街上抠鼻屎的那种。

    (2)按尺寸缩放

    按尺寸缩放是通过Image对象的thumbnail()方法实现的,这里不同于前面直接通过Image调用方法,而是使用Image的具体实例im2调用thumbnail方法,从而对im2直接进行处理。具体代码如下:

    from PIL import Image
    # 打开图像
    im1 = Image.open('xx.jpg')
    # 复制图像
    im2 = im1.copy()
    # 将复制后的图像进行缩放,传入一个元组
    im2.thumbnail((100, 100))
    # 输出图像大小
    print("im1的大小", im1.size)
    print('im2的大小', im2.size)
    

    这里缩放图像并不会对图像进行变形,即显示效果是一样的。这里就不放效果图了,输入结果如下:

    im1的大小 (960, 960)
    im2的大小 (100, 100)
    

    1.5、图像的剪切与粘贴

    (1)图像粘贴

    粘贴的实现主要是通过Image对象的paste(im, box, mask)方法,其中im为Image对象;box为要粘贴到的区域;mask为遮罩(我也不知道啥是遮罩)。其中box的参数有三种形式:

    • (x1, y1):将im左上角对齐(x1,y1)点,其余部分粘贴,超出部分抛弃
    • (x1, x2, y1, y2):将im粘贴至此区域
    • None:此时im必须与源图像大小一致

    (2)裁剪图像

    裁剪主要通过Image对象的crop(box)方法实现,box同粘贴中一致。

    接下来我们做一个小练习,想将图像某个区域剪切下来,然后粘贴到另一个图像上:

    from PIL import Image
    # 打开图像
    im = Image.open('nnz.jpg')
    # 复制两份
    im1 = im.copy()
    im2 = im.copy()
    # 剪切图片
    im_crop = im1.crop((200, 200, 400, 400))
    # 粘贴图片
    im2.paste(im_crop, (30, 30))
    im2.show()
    

    原图和效果图对比如下:

    貌美如花的娜娜子。

    1.4、图像旋转和格式转换

    (1)图像旋转

    图像旋转就非常简单了,简单的一句代码,通过Image对象调用rotate(),该方法返回被旋转图像的一个副本:

    from PIL import Image
    im = Image.open('nnz.jpg')
    # 旋转90度然后显示
    im.rotate(90).show()
    

    顺时针逆时针就不要问我了。

    (2)格式转换

    • convert:转换图像的模式
    • transpose:转换图像的格式

    convert之前已经使用过了,这里就简单演示一下transpose的作用,transpose主要传入一些Image中的常量:

    from PIL import Image
    # 打开图像
    im = Image.open('nnz.jpg')
    # 这里我也不知道注释啥了,总之效果和rotate(90)效果一样
    im.transpose(Image.ROTATE_90).show()
    

    效果图我也就不放了,给大家列出一些可以传入的常量和该常量的作用:

    常量 作用
    Image.FILP_TOP_BOTTOM 上下翻转
    Image.FILP_LEFT_RIGHT 左右翻转
    Image.ROTATE_90 翻转90°
    Image.ROTATE_180 翻转180°
    Image.TRANSPOSE 颠倒

    我也不知道这是哪门子的格式转换。

    1.5、分离和合并

    (1)分离

    这个是之前使用过的,通过Image对象的split()方法,将图像的RGB三个通道分离,并返回三个Image对象:

    from PIL import Image
    # 打开图像
    im = Image.open('nnz.jpg')
    # 分离通道,返回3个Image对象
    r, g, b = im.split()
    

    三个通道的效果图如下:

    (2)合并

    合并是通过Image.merge(mode, bands)方法实现的,其中mode为模式,bands为通道列表,传入一个列表类型数据。下面我实现以下小新多年来的愿望:

    from PIL import Image
    # 打开小新.jpg和娜娜子.jpg
    im1 = Image.open('娜娜子.jpg')
    im2 = Image.open('小新.jpg')
    # 让im2大小和im1一样
    im2.resize(im1.size)
    # 将两个图像分别分离
    r1, g1, b1 = im1.split()
    r2, g2, b2 = im2.split()
    # 合并图像
    im3 = Image.merge('RGB', [r1, g2, b1])
    im3.show()
    

    效果图如下,看到这么美的图片,小新一定会感谢我的:

    到这里,我们就把Image模块的大致内容讲解完了,接下来我们来了解PIL中更丰富的功能。

    二、ImageFilter

    ImageFilter中提供了很多常用的滤镜功能,

    2.1、高斯模糊

    高斯模糊也叫高斯平滑,是啥我也不知道,反正听名字就是模糊。我们结合上面的内容完成一个小案例:

    from PIL import Image, ImageFilter
    # 打开图像
    im1 = Image.open('iron_man.jpg')
    # 创建一个im1两倍宽的图像
    img = Image.new('RGB', (im1.width*2, im1.height), 'red')
    # 高斯模糊处理
    im2 = im1.filter(ImageFilter.GaussianBlur)
    # 将im1粘贴到img上
    img.paste(im1, (0, 0))
    # 将im2(高斯模糊后的图像)粘贴到img上
    img.paste(im2, (im1.width, 0))
    img.show()
    

    为了考虑小新的感受,下面不再用娜娜子作为素材。我选取了一张钢铁侠的图片,运行结果如下:

    希望各位读者不要误会,他俩真没说你帅,他俩只说笔者一个人帅。

    2.2、其它滤镜

    除了高斯模糊,ImageFilter中还提供了许多其它滤镜:

    滤镜值 滤镜名词
    BLUR 模糊效果
    CONTOUR 轮廓
    DETAIL 细节
    EDGE_ENHANCE 边缘增强
    EDGE_ENHANCE_MORE 边缘增强plus
    EMBOSS 浮雕效果
    FIND_EDGES 寻找边缘
    SMOOTH 平滑

    笔者用一张美女图片,测试了上面几个滤镜的效果,发现9张图是看起来是完全一样的。虽然完全一样,但是笔者还是打算将这次测试的结果作为我慈善事业的一部分,分享给各位读者。

    其中1为高斯模糊,2-9分别为表格中的8个滤镜。

    三、ImageChops模块(图像合成)

    ImageChops模块中,提供了很多图像合成的方法。这些方法是通过计算通道中像素值来实现的,不同的方法有不同的计算方式。

    3.1、加法运算

    加法运算通过**ImageChops.add(image1, image2, scale=1.0, offset=0)**方法实现,合成公式如下:

    out = (im1 + im2)/scale + offset
    

    我也看不懂,其中scale和offset是有默认值的。所以使用时我们可以省略参数,具体实现如下:

    from PIL import Image, ImageChops
    # 打开图像
    im1 = Image.open('im1.jpg')
    im2 = Image.open('im2.jpg')
    # 合成图像并显示
    im3 = ImageChops.add(im1, im2)
    im3.show()
    

    实验结果产不忍赌,效果图如下:

    在这里插入图片描述

    3.2、减法运算

    加法运算通过**ImageChops.subtract(image1, image2, scale=1.0, offset=0)**方法实现,合成公式如下:

    out = (im1 - im2)/scale + offset
    

    其使用和add方法是一致的,代码如下:

    from PIL import Image, ImageChops
    # 打开图像
    im1 = Image.open('xscn.jpg')
    im2 = Image.open('xscn2.jpg')
    # 合成图像并显示
    im3 = ImageChops.subtract(im1, im2)
    im3.show()
    

    原本是不想放效果图的,但是运行后,发现效果图比较美,所以想和大家分享一下:

    在这里插入图片描述

    希望大家读到这篇博客的时候是独自一人的深夜。

    3.3、其它函数

    因为大多数函数的使用都比较简单,所以后续的函数也不单独拿出来讲了,具体功效可以看下列表:

    函数名 参数 作用 计算公式
    darker(变暗) (image1, image2) 对比两种图片的像素,取两种图片中对应像素的较小值。(去亮留暗) min(im1, im2)
    lighter(变亮) 同上 对比两种图片的像素,取两种图片中对应像素的较大值。(去暗留亮) max(im1, im2)
    invert(反色) (image) 将max(255)减去每个像素的值 max-image
    multiply(叠加) (image1, image2) 两种图片互相叠加。如果和黑色叠加,将获得一张很色图片 im1*im2/max
    screen(屏幕) 同上 先反色后叠加 max-((max-im1)*(max-im2)/max)
    difference(比较) 同上 各个像素做减法,取绝对值。如果像素相同结果为黑色 abs(im1-im2)

    演示代码如下:

    from PIL import Image, ImageChops
    
    # 打开图像
    im1 = Image.open("im1.jpg")
    im2 = Image.open("im2.jpg")
    
    # 对图像进行各种操作
    im3 = ImageChops.darker(im1, im2)
    im3.save('darker.jpg')
    im3 = ImageChops.lighter(im1, im2)
    im3.save('lighter.jpg')
    im3 = ImageChops.invert(im1)
    im3.save('invert.jpg')
    im3 = ImageChops.multiply(im1, im2)
    im3.save('multiply.jpg')
    im3 = ImageChops.screen(im1, im2)
    im3.save('screen.jpg')
    im3 = ImageChops.difference(im1, im2)
    im3.save('difference.jpg')
    

    其中,我选取的素材im1和im2都是上面使用到的那两张,效果图如下:

    在这里插入图片描述

    这样,我的女神就被我毁的体无完肤了。

    四、ImageEnhance模块(色彩、亮度)

    ImageEnhance提供了许多函数,用于调整图像的色彩、对比度、亮度、清晰度等。调整图像的步骤如下:

    1. 确定要调整的参数,获取特定的调整器
    2. 调用调整器的enhance方法,传入参数进行调整。

    注意:所有调整器都实现同一个接口,该接口中包含一个方法enhance

    其中enhance方法接收一个参数factor,factor是一个大于0的数。当factor为1时,返回原图,当factor小于1返回减弱图,大于1返回增强图。

    各个获取色彩调整器的方法如下:

    方法名称 方法作用
    ImageEnhance.Color() 获取颜色调整器
    ImageEnhance.Contrast() 获取对比度调整器
    ImageEnhance.Brightness() 获取亮度调整器
    ImageEnhance.Sharpness() 获取清晰度调整器

    虽然是很想偷懒,不去做实验,但是想想还是做了如下实验,代码如下:

    from PIL import Image, ImageEnhance
    # 打开im1
    im1 = Image.open("gtx.jpg")
    # 获取颜色(各种)调整器
    enhance_im1 = ImageEnhance.Color(im1)
    #enhance_im1 = ImageEnhance.Contrast(im1)
    #enhance_im1 = ImageEnhance.Brightness(im1)
    #enhance_im1 = ImageEnhance.Sharpness(im1)
    # 减弱颜色(以及其它属性)
    im2 = enhance_im1.enhance(0.5)
    # 增强颜色(以及其它属性)
    im3 = enhance_im1.enhance(1.5)
    
    # 获取原图大小
    w, h = im1.size
    # 创建一个原图大小3倍的图片
    img = Image.new("RGB", (w*3, h))
    # 将减弱的图片放在最左边
    img.paste(im2, (0, 0))
    # 将原图放在中间
    img.paste(im1, (w, 0))
    # 将增强后的图片放在最右边
    img.paste(im3, (w*2, 0))
    # 显示图片
    img.show()
    

    其中,我们只需要修改获取调整器的代码就可以了,获取其它调制器的代码我注释了。然后看看效果图:

    在这里插入图片描述

    这种不伤大雅的工作,让我唐尼叔做再适合不过了。

    另外再讲一个调节亮度的函数,但是这个函数时Image中的函数point(),而不是ImageEnhance的。该函数传入一个参数,使用方法和Image.eval()类似,使用示例如下:

    from PIL import Image
    # 打开图像
    im1 = Image.open('gtx.jpg')
    
    # 变暗操作
    im2 = im1.point(lambda x:x*0.5)
    # 变量操作
    im3 = im1.point(lambda x:x*1.5)
    # 获取原图大小
    w, h = im1.size
    # 创建一个原图大小3倍的图片
    img = Image.new("RGB", (w*3, h))
    # 将减弱的图片放在最左边
    img.paste(im2, (0, 0))
    # 将原图放在中间
    img.paste(im1, (w, 0))
    # 将增强后的图片放在最右边
    img.paste(im3, (w*2, 0))
    # 显示图片
    img.show()
    

    效果图如下:

    在这里插入图片描述

    五、ImageDraw模块

    该模块提供了许多绘制2D图像的功能,我们可以通过绘制获取一个全新的图像,也可以在原有的图像上进行绘制。在我们使用该模块进行绘制时,我们需要先获取ImageDraw.Draw对象,获取方式如下:

    from PIL import ImageDraw
    # 构造函数中,im为一个Image对象
    drawer = ImageDraw.Draw(im)
    

    我们获取ImageDraw.Draw对象后就可以进行相应的绘制了。

    5.1、绘制简单形状

    在绘制之前,我们先创建一个空白的图片:

    from PIL import Image, ImageDraw
    
    # 创建一个300*300的白色图片
    im = Image.new("RGB", (300, 300), "white")
    # 获取ImageDraw.Draw对象
    drawer = ImageDraw.Draw(im)
    

    后续的绘制都可以使用对象drawer绘制。

    (1)绘制直线

    """
    xy:起点坐标和终点坐标(x1, y1, x2, y2)
    fill:填充色。"red"、"blue"...
    width:轮廓粗细
    joint:连接方式,可以是曲线
    """
    line(xy, fill, width, joint)
    # 绘制直线
    drawer.line((50, 50, 150, 150), fill='green',width=2)
    

    (2)绘制矩形

    """
    xy:左上角坐标和右下角坐标(x1, y1, x2, y2)
    fill:填充色。"red"、"blue"...
    outline:轮廓色。同上
    width:轮廓粗细
    """
    rectangle(xy, fill, outline, width)
    # 使用示例
    drawer.rectangle((50, 50, 150, 150), fill='green', outline='red', width=3)
    

    (3)绘制圆弧

    """
    xy:包含圆弧所在圆的矩形的左上角坐标和右下角坐标(x1, y1, x2, y2)
    start:起始角度
    end:终止角度
    fill:填充色。"red"、"blue"...
    width:轮廓粗细
    """
    arc(xy, start, end, fill, width)
    # 使用示例
    drawer.arc((50, 50, 150, 150), start=0, end=90, fill='green', width=3)
    

    对于xy参数的解释如图所示:

    在这里插入图片描述

    (4)绘制椭圆

    """
    xy:包含椭圆(或圆)的矩形的左上角坐标和右下角坐标(x1, y1, x2, y2)
    fill:填充色。"red"、"blue"...
    outline:轮廓颜色
    width:轮廓粗细
    """
    ellipse(xy, fill, outline, width)
    # 使用示例
    drawer.ellipse((50, 50, 150, 150),fill='green', outline='red', width=3)
    

    (5)绘制弦

    
    """
    xy:弦所在椭圆的矩形的左上角坐标和右下角坐标(x1, y1, x2, y2)
    start:开始角度
    end:终点角度
    fill:填充色。"red"、"blue"...
    outline:轮廓颜色
    width:轮廓粗细
    """
    chord(xy, start, end, fill, outline, width)
    # 使用示例
    drawer.chord((50, 50, 150, 150),start=0, end=90, fill='green', outline='red', width=3)
    

    (6)绘制扇形

    
    """
    xy:扇形所在椭圆的矩形的左上角坐标和右下角坐标(x1, y1, x2, y2)
    start:开始角度
    end:终点角度
    fill:填充色。"red"、"blue"...
    outline:轮廓颜色
    width:轮廓粗细
    """
    pieslice(xy, start, end, fill, outline, width)
    # 使用示例
    drawer.pieslice((50, 50, 150, 150),start=0, end=90, fill='green', outline='red', width=3)
    

    (7)绘制多边形

    """
    xy:多边形各个点坐标的元组/列表(x1, y1, x2, y2)
    fill:填充色。"red"、"blue"...
    outline:轮廓颜色
    """
    pieslice(xy, fill, outline)
    # 使用示例
    drawer.polygon((50, 50, 150, 150, 150, 200, 200, 250, 50, 50), fill='green', outline='red')
    

    (8)绘制点

    """
    xy:点的坐标
    fill:填充色。"red"、"blue"...
    """
    point(xy, fill)
    # 使用示例
    drawer.point((100, 100), fill='black')
    

    除了上面这些简单图形外,我们还可以使用Draw绘制文字。

    5.2、绘制文字

    绘制文字和绘制图形是一样的:

    """
    xy:起点坐标
    text:绘制的文本
    fill:填充色。"red"、"blue"...
    ...其中绘制文字还有许多其它参数
    """
    text(xy, text, fill)
    # 使用示例
    drawer.text((100, 100), text='zack' fill='red')
    

    当我们绘制中文时,上述代码会报错,因为默认编码是不支持中文的。我们可以在C:/Windows/Fonts目录下找到字体文件,我们选择一个支持中文的。我这里直接是将字体文件复制到项目底下来了,代码如下:

    from PIL import Image, ImageDraw, ImageFont
    # 创建一个图像用于绘制文字
    im = Image.new("RGB", (300, 300), "white")
    drawer = ImageDraw.Draw(im)
    # 获取字体对象
    imFont = ImageFont.truetype('simkai.ttf', 30)
    # 绘制文字时设置字体
    drawer.text((50, 100),text="啥",font=imFont,fill="red")
    im.show()
    

    最后送大家一套2020最新Pyhon项目实战视频教程,点击此处 进来获取 跟着练习下,希望大家一起进步哦!

    我们使用了ImageFont.truetype()函数获取字体对象,在获取时我们可以设置字体大小。到此我们就了解了PIL的各种操作了,感兴趣的读者可以关注我的个人公众号:ZackSock。

    展开全文
  • Python图像处理基础

    千次阅读 多人点赞 2018-01-19 11:38:10
    Python图像处理基础 对我个人而言使用Python图像处理意在取代matlab,集中化使用Python环境保证之后在机器学习和OpenCV的使用上具有一致性,虽然从实验室师兄师姐的口中得知Python的图像处理较之matlab相对复杂...

    Python图像处理基础

    对我个人而言使用Python图像处理意在取代matlab,集中化使用Python环境保证之后在机器学习和OpenCV的使用上具有一致性,虽然从实验室师兄师姐的口中得知Python的图像处理较之matlab相对复杂(应该只是代码量的问题),但我依然觉得学习python环境比较实用和高效。在进行Python图像处理之前,Pillow是不可或缺的实用性工具,pillow是Python Imaging Library的缩写,Pillow由PIL而来,导入该库使用import PIL。同时感谢Python社区内的翻译工作者,将pillow的英文稳当翻译为汉语文档。传统的PIL库不支持python3,所以使用从PIL派生出来的Pillow库。

    从学习PIL开始

    Python Imaging Library ( PIL ) 给 Python 增加了图像处理能力。这个库提供了广泛的文件格式支持,高效的内部展现,以及十分强大的图像处理能力。以下为我们常用的图像处理功能:

    • 图像存储
      PIL 设计用于图像归档和图像批量处理,可以使用它建立缩略图,转换格式,打印图片等。现在的版本可以验证和读取大量的图片格式。写入有意设计为只能写入常用的文件格式。
    • 图像显示
      现在的版本包含了 Tk PhotoImage 和 BitmapImage 接口, 以及 Windows DIB interface ,这有助于在 Windows 下使用。
      为了方便测试,还提供了 show() 方法,可以保存图像到磁盘并显示。
    • 图像处理
      这个库包含了基本的图像处理功能,包括点操作,使用内置卷积内核过滤,色彩空间转换。支持更改图像大小、旋转、自由变换。有一个直方图方法允许你统计图像,这可以用于对比度增强和全局统计分析。

    使用 Image 类

    要从文件加载图像,使用 open() 函数, 在 Image 模块:

        @zhangziju
        from PIL import Image             ##调用库
        im = Image.open("E:\mywife.jpg")  ##文件存在的路径
        im.show()                         

    我老婆

    需要知道的是在win的环境下im.show的方式为win自带的图像显示应用。

    @zhangziju
    from PIL import Image
    im = Image.open("E:\mywife.jpg")
    print(im.format, im.size, im.mode) ## 打印出关键信息
    im.show()

    JPEG (750, 560) RGB ## 输出关键信息 同时再次看到我老婆

    format 这个属性标识了图像来源。如果图像不是从文件读取它的值就是None。
    size属性是一个二元元组,包含width、height(宽度和高度,单位都是px)。mode 属性定义了图像bands的数量和名称,以及像素类型和深度。
    常见的modes 有 “L” (luminance) 表示灰度图像,“RGB”表示真彩色图像,和 “CMYK” 表示出版图像。如下表为常见的nodes描述。

    modes 描述
    1 1位像素,黑和白,存成8位的像素
    L 8位像素,黑白
    P 8位像素,使用调色板映射到任何其他模式
    RGB 3× 8位像素,真彩
    RGBA 4×8位像素,真彩+透明通道
    CMYK 4×8位像素,颜色隔离
    YCbCr 3×8位像素,彩色视频格式
    I 32位整型像素
    F 32位浮点型像素

    读写图像

    PIL 模块对目前存在的大多数图片格式都支持,一般使用 Image 模块的 open() 函数从磁盘读取文件,并不需要知道文件格式就能打开它,这个库能够根据文件内容自动确定文件格式。若要保存文件,则使用 Image 类的 save() 方法,此时保存文件的文件名就变得十分重要了,除非你指定格式,否则这个库将会以文件名的扩展名作为格式保存

    @zhangziju
    from PIL import Image
    im = Image.open("E:\mywife.jpg")
    print(im)
    im.save("E:\mywife.png")     ## 将"E:\mywife.jpg"保存为"E:\mywife.png"
    im = Image.open("E:\mywife.png")  ##打开新的png图片
    print(im.format, im.size, im.mode)

    如下图,在指定路径下可看到新保存的png格式的图片。
    新的png图片
    此时在查看Python的输出信息

    PNG (750, 560) RGB

    通常而言,save用以保存一个临时的image对象到硬盘。而转换工作由一个功能更为强大的convert()方法来完成。convert()是将图像转换格式的类。

    @zhangziju
    from PIL import Image
    im = Image.open("E:\mywife.jpg")
    new_im = im.convert('L')               ##可以看到“L”模式的我老婆,即黑白模式
    new_im.show()                          ##用以显示

    “L”模式的gakki,同理,可转换其余几种格式,此处略去。

    黑白

    裁剪、复制、合并等操作

    首先引入box的概念,box变量是一个四元组(左,上,右,下)。用来表示在原始图像中截取的位置坐标,如box(100,100,200,200)就表示在原始图像中以左上角为坐标原点,截取一个100*100(像素为单位)的图像,为方便理解,如下为示意图box(b1,a1,b2,a2)。作图软件为Visio2016。
    box区域示意图

    @zhangziju
    from PIL import Image
    im = Image.open("E:\mywife.jpg")
    box = (300, 100, 700, 700)              ##确定拷贝区域大小
    region = im.crop(box)                   ##将im表示的图片对象拷贝到region中,大小为box
    region.show()

    如下图为box截取的图像区域显示。

    box区域图
    crop函数带的参数为(起始点的横坐标,起始点的纵坐标,宽度,高度)。paste函数的参数为(需要修改的图片,粘贴的起始点的横坐标,粘贴的起始点的纵坐标)。(待补充)

    @zhangziju
    from PIL import Image
    im = Image.open("E:\mywife.jpg")
    box = (300, 100, 700, 700)
    region = im.crop(box)
    region = region.transpose(Image.ROTATE_180) ##将拷贝的小图旋转180度再放入region中
    im.paste(region, box)                       ##粘贴box大小的region到原先的图片对象中。
    im.show()

    box = im.copy( ) #直接复制图像

    如下图所示为旋转和粘贴操作。

    粘贴截取图形

    几何变换

    Image类有resize()、rotate()和transpose()、transform()方法进行几何变换,用以重定义图片大小,对图片进行旋转等操作。

    @zhangziju
    from PIL import Image
    im = Image.open("E:\mywife.jpg")
    region = im.resize((400, 400))     ##重新设定大小
    region.show()

    很明显由于大小的重新设定,图片的显示效果有所转变,gakki依然美腻~
    设定大小
    还可以做以下操作:

    @zhangziju
    im.rotate(45)                             #逆时针旋转 45 度角。
    im.transpose(Image.FLIP_LEFT_RIGHT)       #左右对换。
    im.transpose(Image.FLIP_TOP_BOTTOM)       #上下对换。
    im.transpose(Image.ROTATE_90)             #旋转 90 度角。
    im.transpose(Image.ROTATE_180)            #旋转 180 度角。
    im.transpose(Image.ROTATE_270)            #旋转 270 度角。

    图像滤波

    图像滤波在ImageFilter 模块中,在该模块中,预先定义了很多增强滤波器,可以通过filter( )函数使用,预定义滤波器包括:BLUR、CONTOUR、DETAIL、EDGE_ENHANCE、EDGE_ENHANCE_MORE、EMBOSS、FIND_EDGES、SMOOTH、SMOOTH_MORE、SHARPEN。其中BLUR就是均值滤波,CONTOUR找轮廓,FIND_EDGES边缘检测,使用该模块时,需先导入。还有其他功能强大的滤波效果,此处略。

    @zhangziju
    from PIL import Image
    from PIL import ImageFilter                         ## 调取ImageFilter
    imgF = Image.open("E:\mywife.jpg")
    bluF = imgF.filter(ImageFilter.BLUR)                ##均值滤波
    conF = imgF.filter(ImageFilter.CONTOUR)             ##找轮廓
    edgeF = imgF.filter(ImageFilter.FIND_EDGES)         ##边缘检测
    imgF.show()
    bluF.show()
    conF.show()
    edgeF.show()

    滤波处理下的gakki~

    滤波

    图像增强

    Pillow中的图像增强函数主要在ImageEnhance模块下,通过该模块可以调节图像的颜色、对比度和饱和度和锐化等。ImageEnhance模块的接口所有的增强类都实现了一个通用的接口,包括一个方法:

    enhancer.enhance(factor) ⇒ image

    该方法返回一个增强过的图像。变量factor是一个浮点数,控制图像的增强程度。变量factor为1将返回原始图像的拷贝;factor值越小,颜色越少(亮度,对比度等),同时对变量facotr并没有限制。

    Color类

    颜色增强类用于调整图像的颜色均衡,该类实现的增强接口如下:

    ImageEnhance.Color(image) ⇒ Color enhancer instance

    创建一个增强对象,以调整图像的颜色。增强因子为0.0将产生黑白图像;为1.0将给出原始图像。

    @zhangziju
    from PIL import Image
    from PIL import ImageEnhance
    imcor=Image.open("E:\mywife.jpg")
    im_01 = ImageEnhance.Color(imcor).enhance(0.1)
    im_10 = ImageEnhance.Color(imcor).enhance(1.0)
    im_30 =ImageEnhance.Color(imcor).enhance(3.0)
    im_01.show()
    im_10.show()
    im_30.show()

    如下图为ImageEnhance.Color(image)在取值0.1、1.0和3.0时的效果图。

    cor

    Brightness类

    亮度增强类用于调整图像的亮度,该类实现的增强接口如下:

    ImageEnhance.Brightness(image)⇒ Brightnessenhancer instance

    创建一个调整图像亮度的增强对象。增强因子为0.0将产生黑色图像;为1.0将保持原始图像。

    @zhangziju
    from PIL import Image
    from PIL import ImageEnhance
    imbri =Image.open("E:\mywife.jpg")
    im_02 = ImageEnhance.Brightness(imbri).enhance(0.2)
    im_08 = ImageEnhance.Brightness(imbri).enhance(0.8)
    im_20 = ImageEnhance.Brightness(imbri).enhance(2.0)
    im_02.show()
    im_08.show()
    im_20.show()

    如下图为ImageEnhance.Brightness(image)在取值0.2、0.8和2.0时的效果图。

    bri

    Contrast类

    对比度增强类用于调整图像的对比度。该类实现的增强接口如下:

    ImageEnhance.Contrast(image) ⇒ Contrast enhancer instance

    创建一个调整图像对比度的增强对象。增强因子为0.0将产生纯灰色图像;为1.0将保持原始图像。

    @zhangziju
    from PIL import Image
    from PIL import ImageEnhance
    imcon =Image.open("E:\mywife.jpg")
    im_02 = ImageEnhance.Contrast(imcon).enhance(0.2)
    im_08 =ImageEnhance.Contrast (imcon).enhance(0.8)
    im_20 =ImageEnhance.Contrast (imcon).enhance(2.0)
    im_02.show()
    im_08.show()
    im_20.show()

    如下图为ImageEnhance.Contrast(image)在取值0.2、0.8和2.0时的效果图。

    con
    同样的还有Sharpness锐度增强类用于调整图像的锐度。此处略。

    展开全文
  • python 图像处理

    千次阅读 2018-06-19 14:48:01
    转自:点击打开链接第 1 章 基本的图像操作和处理本章讲解操作和处理图像的基础知识,将通过大量...1.1 PIL:Python图像处理类库PIL(Python Imaging Library Python,图像处理类库)提供了通用的图像处理功能,以...

    转自:

    点击打开链接

    第 1 章 基本的图像操作和处理

    本章讲解操作和处理图像的基础知识,将通过大量示例介绍处理图像所需的 Python 工具包,并介绍用于读取图像、图像转换和缩放、计算导数、画图和保存结果等的基本工具。这些工具的使用将贯穿本书的剩余章节。

    1.1 PIL:Python图像处理类库

    PIL(Python Imaging Library Python,图像处理类库)提供了通用的图像处理功能,以及大量有用的基本图像操作,比如图像缩放、裁剪、旋转、颜色转换等。PIL 是免费的,可以从http://www.pythonware.com/products/pil/ 下载。

    利用 PIL 中的函数,我们可以从大多数图像格式的文件中读取数据,然后写入最常见的图像格式文件中。PIL 中最重要的模块为 Image。要读取一幅图像,可以使用:

    from PIL import Image
    
    pil_im = Image.open('empire.jpg')
    
    

    上述代码的返回值 pil_im 是一个 PIL 图像对象。

    图像的颜色转换可以使用 convert() 方法来实现。要读取一幅图像,并将其转换成灰度图像,只需要加上 convert('L'),如下所示:

    pil_im = Image.open('empire.jpg').convert('L')
    
    

    在 PIL 文档中有一些例子,参见http://www.pythonware.com/library/pil/handbook/index.htm。这些例子的输出结果如图 1-1 所示。

    图 1-1:用 PIL 处理图像的例子

    1.1.1 转换图像格式

    通过 save() 方法,PIL 可以将图像保存成多种格式的文件。下面的例子从文件名列表(filelist)中读取所有的图像文件,并转换成 JPEG 格式:

    from PIL import Image
    import os
    
    for infile in filelist:
      outfile = os.path.splitext(infile)[0] + ".jpg"
      if infile != outfile:
        try:
          Image.open(infile).save(outfile)
        except IOError:
          print "cannot convert", infile
    
    

    PIL 的 open() 函数用于创建 PIL 图像对象,save() 方法用于保存图像到具有指定文件名的文件。除了后缀变为“.jpg”,上述代码的新文件名和原文件名相同。PIL 是个足够智能的类库,可以根据文件扩展名来判定图像的格式。PIL 函数会进行简单的检查,如果文件不是 JPEG 格式,会自动将其转换成 JPEG 格式;如果转换失败,它会在控制台输出一条报告失败的消息。

    本书会处理大量图像列表。下面将创建一个包含文件夹中所有图像文件的文件名列表。首先新建一个文件,命名为 imtools.py,来存储一些经常使用的图像操作,然后将下面的函数添加进去:

    import os
    def get_imlist(path):
    
    """ 返回目录中所有JPG 图像的文件名列表"""
    
    return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')]
    
    

    现在,回到 PIL。

    1.1.2 创建缩略图

    使用 PIL 可以很方便地创建图像的缩略图。thumbnail() 方法接受一个元组参数(该参数指定生成缩略图的大小),然后将图像转换成符合元组参数指定大小的缩略图。例如,创建最长边为 128 像素的缩略图,可以使用下列命令:

    pil_im.thumbnail((128,128))
    
    

    1.1.3 复制和粘贴图像区域

    使用 crop() 方法可以从一幅图像中裁剪指定区域:

    box = (100,100,400,400)
    region = pil_im.crop(box)
    
    

    该区域使用四元组来指定。四元组的坐标依次是(左,上,右,下)。PIL 中指定坐标系的左上角坐标为(0,0)。我们可以旋转上面代码中获取的区域,然后使用 paste() 方法将该区域放回去,具体实现如下:

    region = region.transpose(Image.ROTATE_180)
    pil_im.paste(region,box)
    
    

    1.1.4 调整尺寸和旋转

    要调整一幅图像的尺寸,我们可以调用 resize() 方法。该方法的参数是一个元组,用来指定新图像的大小:

    out = pil_im.resize((128,128))
    
    

    要旋转一幅图像,可以使用逆时针方式表示旋转角度,然后调用 rotate() 方法:

    out = pil_im.rotate(45)
    
    

    上述例子的输出结果如图 1-1 所示。最左端是原始图像,然后是灰度图像、粘贴有旋转后裁剪图像的原始图像,最后是缩略图。

    1.2 Matplotlib

    我们处理数学运算、绘制图表,或者在图像上绘制点、直线和曲线时,Matplotlib 是个很好的类库,具有比 PIL 更强大的绘图功能。Matplotlib 可以绘制出高质量的图表,就像本书中的许多插图一样。Matplotlib 中的 PyLab 接口包含很多方便用户创建图像的函数。Matplotlib 是开源工具,可以从 http://matplotlib.sourceforge.net/ 免费下载。该链接中包含非常详尽的使用说明和教程。下面的例子展示了本书中需要使用的大部分函数。

    1.2.1 绘制图像、点和线

    尽管 Matplotlib 可以绘制出较好的条形图、饼状图、散点图等,但是对于大多数计算机视觉应用来说,仅仅需要用到几个绘图命令。最重要的是,我们想用点和线来表示一些事物,比如兴趣点、对应点以及检测出的物体。下面是用几个点和一条线绘制图像的例子:

    from PIL import Image
    from pylab import *
    
    # 读取图像到数组中
    im = array(Image.open('empire.jpg'))
    
    # 绘制图像
    imshow(im)
    
    # 一些点
    x = [100,100,400,400]
    y = [200,500,200,500]
    
    # 使用红色星状标记绘制点
    plot(x,y,'r*')
    
    # 绘制连接前两个点的线
    plot(x[:2],y[:2])
    
    # 添加标题,显示绘制的图像
    title('Plotting: "empire.jpg"')
    show()
    
    

    上面的代码首先绘制出原始图像,然后在 x 和 y 列表中给定点的 x 坐标和 y 坐标上绘制出红色星状标记点,最后在两个列表表示的前两个点之间绘制一条线段(默认为蓝色)。该例子的绘制结果如图 1-2 所示。show() 命令首先打开图形用户界面(GUI),然后新建一个图像窗口。该图形用户界面会循环阻断脚本,然后暂停,直到最后一个图像窗口关闭。在每个脚本里,你只能调用一次show() 命令,而且通常是在脚本的结尾调用。注意,在 PyLab 库中,我们约定图像的左上角为坐标原点。

    图像的坐标轴是一个很有用的调试工具;但是,如果你想绘制出较美观的图像,加上下列命令可以使坐标轴不显示:

    axis('off')
    
    

    上面的命令将绘制出如图 1-2 右边所示的图像。

    图 1-2:Matplotlib 绘图示例。带有坐标轴和不带坐标轴的包含点和一条线段的图像

    在绘图时,有很多选项可以控制图像的颜色和样式。最有用的一些短命令如表 1-1、表 1-2 和表 1-3 所示。使用方法见下面的例子:

    plot(x,y)         # 默认为蓝色实线
    plot(x,y,'r*')    # 红色星状标记
    plot(x,y,'go-')   # 带有圆圈标记的绿线
    plot(x,y,'ks:')   # 带有正方形标记的黑色虚线
    
    

    表1-1:用PyLab库绘图的基本颜色格式命令

    颜色

     

    'b'

    蓝色

    'g'

    绿色

    'r'

    红色

    'c'

    青色

    'm'

    品红

    'y'

    黄色

    'k'

    黑色

    'w'

    白色

    表1-2:用PyLab库绘图的基本线型格式命令

    线型

     

    '-'

    实线

    '--'

    虚线

    ':'

    点线

    表1-3:用PyLab库绘图的基本绘制标记格式命令

    标记

     

    '.'

    'o'

    圆圈

    's'

    正方形

    '*'

    星形

    '+'

    加号

    'x'

    叉号

    1.2.2 图像轮廓和直方图

    下面来看两个特别的绘图示例:图像的轮廓和直方图。绘制图像的轮廓(或者其他二维函数的等轮廓线)在工作中非常有用。因为绘制轮廓需要对每个坐标 [x, y] 的像素值施加同一个阈值,所以首先需要将图像灰度化:

    from PIL import Image
    from pylab import *
    
    # 读取图像到数组中
    im = array(Image.open('empire.jpg').convert('L'))
    
    # 新建一个图像
    figure()
    # 不使用颜色信息
    gray()
    # 在原点的左上角显示轮廓图像
    contour(im, origin='image')
    axis('equal')
    axis('off')
    
    

    像之前的例子一样,这里用 PIL 的 convert() 方法将图像转换成灰度图像。

    图像的直方图用来表征该图像像素值的分布情况。用一定数目的小区间(bin)来指定表征像素值的范围,每个小区间会得到落入该小区间表示范围的像素数目。该(灰度)图像的直方图可以使用hist() 函数绘制:

    figure()
    hist(im.flatten(),128)
    show()
    
    

    hist() 函数的第二个参数指定小区间的数目。需要注意的是,因为 hist() 只接受一维数组作为输入,所以我们在绘制图像直方图之前,必须先对图像进行压平处理。flatten() 方法将任意数组按照行优先准则转换成一维数组。图 1-3 为等轮廓线和直方图图像。

    图 1-3:用 Matplotlib 绘制图像等轮廓线和直方图

    1.2.3 交互式标注

    有时用户需要和某些应用交互,例如在一幅图像中标记一些点,或者标注一些训练数据。PyLab库中的 ginput() 函数就可以实现交互式标注。下面是一个简短的例子:

    from PIL import Image
    from pylab import *
    
    im = array(Image.open('empire.jpg'))
    imshow(im)
    print 'Please click 3 points'
    x = ginput(3)
    print 'you clicked:',x
    show()
    
    

    上面的脚本首先绘制一幅图像,然后等待用户在绘图窗口的图像区域点击三次。程序将这些点击的坐标 [x, y] 自动保存在 x 列表里。

    1.3 NumPy

    NumPyhttp://www.scipy.org/NumPy/)是非常有名的 Python 科学计算工具包,其中包含了大量有用的思想,比如数组对象(用来表示向量、矩阵、图像等)以及线性代数函数。NumPy 中的数组对象几乎贯穿用于本书的所有例子中 1 数组对象可以帮助你实现数组中重要的操作,比如矩阵乘积、转置、解方程系统、向量乘积和归一化,这为图像变形、对变化进行建模、图像分类、图像聚类等提供了基础。

    1PyLab 实际上包含 NumPy 的一些内容,如数组类型。这也是我们能够在 1.2 节使用数组类型的原因。

    NumPy 可以从 http://www.scipy.org/Download 免费下载,在线说明文档(http://docs.scipy.org/doc/numpy/)包含了你可能遇到的大多数问题的答案。关于 NumPy 的更多内容,请参考开源书籍 [24]。

    1.3.1 图像数组表示

    在先前的例子中,当载入图像时,我们通过调用 array() 方法将图像转换成 NumPy 的数组对象,但当时并没有进行详细介绍。NumPy 中的数组对象是多维的,可以用来表示向量、矩阵和图像。一个数组对象很像一个列表(或者是列表的列表),但是数组中所有的元素必须具有相同的数据类型。除非创建数组对象时指定数据类型,否则数据类型会按照数据的类型自动确定。

    对于图像数据,下面的例子阐述了这一点:

    im = array(Image.open('empire.jpg'))
    print im.shape, im.dtype
    
    im = array(Image.open('empire.jpg').convert('L'),'f')
    print im.shape, im.dtype
    
    

    控制台输出结果如下所示:

    (800, 569, 3) uint8
    (800, 569) float32
    
    

    每行的第一个元组表示图像数组的大小(行、列、颜色通道),紧接着的字符串表示数组元素的数据类型。因为图像通常被编码成无符号八位整数(uint8),所以在第一种情况下,载入图像并将其转换到数组中,数组的数据类型为“uint8”。在第二种情况下,对图像进行灰度化处理,并且在创建数组时使用额外的参数“f”;该参数将数据类型转换为浮点型。关于更多数据类型选项,可以参考图书 [24]。注意,由于灰度图像没有颜色信息,所以在形状元组中,它只有两个数值。

    数组中的元素可以使用下标访问。位于坐标 ij,以及颜色通道 k 的像素值可以像下面这样访问:

    value = im[i,j,k]
    
    

    多个数组元素可以使用数组切片方式访问。切片方式返回的是以指定间隔下标访问该数组的元素值。下面是有关灰度图像的一些例子:

    im[i,:] = im[j,:]      # 将第 j 行的数值赋值给第 i 行
    im[:,i] = 100          # 将第 i 列的所有数值设为100
    im[:100,:50].sum()     # 计算前100 行、前 50 列所有数值的和
    im[50:100,50:100]      # 50~100 行,50~100 列(不包括第 100 行和第 100 列)
    im[i].mean()           # 第 i 行所有数值的平均值
    im[:,-1]               # 最后一列
    im[-2,:] (or im[-2])   # 倒数第二行
    
    

    注意,示例仅仅使用一个下标访问数组。如果仅使用一个下标,则该下标为行下标。注意,在最后几个例子中,负数切片表示从最后一个元素逆向计数。我们将会频繁地使用切片技术访问像素值,这也是一个很重要的思想。

    我们有很多操作和方法来处理数组对象。本书将在使用到的地方逐一介绍。你可以查阅在线文档或者开源图书 [24] 获取更多信息。

    1.3.2 灰度变换

    将图像读入 NumPy 数组对象后,我们可以对它们执行任意数学操作。一个简单的例子就是图像的灰度变换。考虑任意函数 f,它将 0...255 区间(或者 0...1 区间)映射到自身(意思是说,输出区间的范围和输入区间的范围相同)。下面是关于灰度变换的一些例子:

    from PIL import Image
    from numpy import *
    
    im = array(Image.open('empire.jpg').convert('L'))
    
    im2 = 255 - im # 对图像进行反相处理
    
    im3 = (100.0/255) * im + 100 # 将图像像素值变换到100...200 区间
    
    im4 = 255.0 * (im/255.0)**2 # 对图像像素值求平方后得到的图像
    
    

    第一个例子将灰度图像进行反相处理;第二个例子将图像的像素值变换到 100...200 区间;第三个例子对图像使用二次函数变换,使较暗的像素值变得更小。图 1-4 为所使用的变换函数图像。图 1-5 是输出的图像结果。你可以使用下面的命令查看图像中的最小和最大像素值:

    print int(im.min()), int(im.max())
    
    

    图 1-4:灰度变换示例。三个例子中所使用函数的图像,其中虚线表示恒等变换

    图 1-5:灰度变换。对图像应用图 1-4 中的函数:f(x)=255-x 对图像进行反相处理(左);f(x)=(100/255)x+100 对图像进行变换(中);f(x)=255(x/255)2 对图像做二次变换(右)

    如果试着对上面例子查看最小值和最大值,可以得到下面的输出结果:

    2 255
    0 253
    100 200
    0 255
    
    

    array() 变换的相反操作可以使用 PIL 的 fromarray() 函数完成:

    pil_im = Image.fromarray(im)
    
    

    如果你通过一些操作将“uint8”数据类型转换为其他数据类型,比如之前例子中的 im3 或者 im4,那么在创建 PIL 图像之前,需要将数据类型转换回来:

    pil_im = Image.fromarray(uint8(im))
    
    

    如果你并不十分确定输入数据的类型,安全起见,应该先转换回来。注意,NumPy 总是将数组数据类型转换成能够表示数据的“最低”数据类型。对浮点数做乘积或除法操作会使整数类型的数组变成浮点类型。

    1.3.3 图像缩放

    NumPy 的数组对象是我们处理图像和数据的主要工具。想要对图像进行缩放处理没有现成简单的方法。我们可以使用之前 PIL 对图像对象转换的操作,写一个简单的用于图像缩放的函数。把下面的函数添加到 imtool.py 文件里:

    def imresize(im,sz):
      """ 使用PIL 对象重新定义图像数组的大小"""
      pil_im = Image.fromarray(uint8(im))
    
      return array(pil_im.resize(sz))
    
    

    我们将会在接下来的内容中使用这个函数。

    1.3.4 直方图均衡化

    图像灰度变换中一个非常有用的例子就是直方图均衡化。直方图均衡化是指将一幅图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同。在对图像做进一步处理之前,直方图均衡化通常是对图像灰度值进行归一化的一个非常好的方法,并且可以增强图像的对比度。

    在这种情况下,直方图均衡化的变换函数是图像中像素值的累积分布函数(cumulative distribution function,简写为 cdf,将像素值的范围映射到目标范围的归一化操作)。

    下面的函数是直方图均衡化的具体实现。将这个函数添加到 imtool.py 里:

    def histeq(im,nbr_bins=256):
      """ 对一幅灰度图像进行直方图均衡化"""
    
      # 计算图像的直方图
      imhist,bins = histogram(im.flatten(),nbr_bins,normed=True)
      cdf = imhist.cumsum() # cumulative distribution function
      cdf = 255 * cdf / cdf[-1] # 归一化
    
      # 使用累积分布函数的线性插值,计算新的像素值
      im2 = interp(im.flatten(),bins[:-1],cdf)
    
      return im2.reshape(im.shape), cdf
    
    

    该函数有两个输入参数,一个是灰度图像,一个是直方图中使用小区间的数目。函数返回直方图均衡化后的图像,以及用来做像素值映射的累积分布函数。注意,函数中使用到累积分布函数的最后一个元素(下标为 -1),目的是将其归一化到 0...1 范围。你可以像下面这样使用该函数:

    from PIL import Image
    from numpy import *
    
    im = array(Image.open('AquaTermi_lowcontrast.jpg').convert('L'))
    im2,cdf = imtools.histeq(im)
    
    

    图 1-6 和图 1-7 为上面直方图均衡化例子的结果。上面一行显示的分别是直方图均衡化之前和之后的灰度直方图,以及累积概率分布函数映射图像。可以看到,直方图均衡化后图像的对比度增强了,原先图像灰色区域的细节变得清晰。

    图 1-6:直方图均衡化示例。左侧为原始图像和直方图,中间图为灰度变换函数,右侧为直方图均衡化后的图像和相应直方图

    图 1-7:直方图均衡化示例。左侧为原始图像和直方图,中间图为灰度变换函数,右侧为直方图均衡化后的图像和相应直方图

    1.3.5 图像平均

    图像平均操作是减少图像噪声的一种简单方式,通常用于艺术特效。我们可以简单地从图像列表中计算出一幅平均图像。假设所有的图像具有相同的大小,我们可以将这些图像简单地相加,然后除以图像的数目,来计算平均图像。下面的函数可以用于计算平均图像,将其添加到 imtool.py 文件里:

    def compute_average(imlist):
      """ 计算图像列表的平均图像"""
    
      # 打开第一幅图像,将其存储在浮点型数组中
      averageim = array(Image.open(imlist[0]), 'f')
    
      for imname in imlist[1:]:
        try:
          averageim += array(Image.open(imname))
        except:
          print imname + '...skipped'
      averageim /= len(imlist)
    
      # 返回uint8 类型的平均图像
      return array(averageim, 'uint8')
    
    

    该函数包括一些基本的异常处理技巧,可以自动跳过不能打开的图像。我们还可以使用 mean() 函数计算平均图像。mean() 函数需要将所有的图像堆积到一个数组中;也就是说,如果有很多图像,该处理方式需要占用很多内存。我们将会在下一节中使用该函数。

    1.3.6 图像的主成分分析(PCA)

    PCA(Principal Component Analysis,主成分分析)是一个非常有用的降维技巧。它可以在使用尽可能少维数的前提下,尽量多地保持训练数据的信息,在此意义上是一个最佳技巧。即使是一幅 100×100 像素的小灰度图像,也有 10 000 维,可以看成 10 000 维空间中的一个点。一兆像素的图像具有百万维。由于图像具有很高的维数,在许多计算机视觉应用中,我们经常使用降维操作。PCA 产生的投影矩阵可以被视为将原始坐标变换到现有的坐标系,坐标系中的各个坐标按照重要性递减排列。

    为了对图像数据进行 PCA 变换,图像需要转换成一维向量表示。我们可以使用 NumPy 类库中的flatten() 方法进行变换。

    将变平的图像堆积起来,我们可以得到一个矩阵,矩阵的一行表示一幅图像。在计算主方向之前,所有的行图像按照平均图像进行了中心化。我们通常使用 SVD(Singular Value Decomposition,奇异值分解)方法来计算主成分;但当矩阵的维数很大时,SVD 的计算非常慢,所以此时通常不使用 SVD 分解。下面就是 PCA 操作的代码:

    from PIL import Image
    from numpy import *
    
    def pca(X):
      """ 主成分分析:
        输入:矩阵X ,其中该矩阵中存储训练数据,每一行为一条训练数据
        返回:投影矩阵(按照维度的重要性排序)、方差和均值"""
    
      # 获取维数
      num_data,dim = X.shape
    
      # 数据中心化
      mean_X = X.mean(axis=0)
      X = X - mean_X
    
    if dim>num_data:
      # PCA- 使用紧致技巧
      M = dot(X,X.T) # 协方差矩阵
      e,EV = linalg.eigh(M) # 特征值和特征向量
      tmp = dot(X.T,EV).T # 这就是紧致技巧
      V = tmp[::-1] # 由于最后的特征向量是我们所需要的,所以需要将其逆转
      S = sqrt(e)[::-1] # 由于特征值是按照递增顺序排列的,所以需要将其逆转
      for i in range(V.shape[1]):
        V[:,i] /= S
    else:
      # PCA- 使用SVD 方法
      U,S,V = linalg.svd(X)
      V = V[:num_data] # 仅仅返回前nun_data 维的数据才合理
    
    # 返回投影矩阵、方差和均值
    return V,S,mean_X
    
    

    该函数首先通过减去每一维的均值将数据中心化,然后计算协方差矩阵对应最大特征值的特征向量,此时可以使用简明的技巧或者 SVD 分解。这里我们使用了 range() 函数,该函数的输入参数为一个整数 n,函数返回整数 0...(n-1) 的一个列表。你也可以使用 arange() 函数来返回一个数组,或者使用 xrange() 函数返回一个产生器(可能会提升速度)。我们在本书中贯穿使用range() 函数。

    如果数据个数小于向量的维数,我们不用 SVD 分解,而是计算维数更小的协方差矩阵 XXT 的特征向量。通过仅计算对应前 kk 是降维后的维数)最大特征值的特征向量,可以使上面的 PCA 操作更快。由于篇幅所限,有兴趣的读者可以自行探索。矩阵 V 的每行向量都是正交的,并且包含了训练数据方差依次减少的坐标方向。

    我们接下来对字体图像进行 PCA 变换。fontimages.zip 文件包含采用不同字体的字符 a 的缩略图。所有的 2359 种字体可以免费下载 2。假定这些图像的名称保存在列表 imlist 中,跟之前的代码一起保存传在 pca.py 文件中,我们可以使用下面的脚本计算图像的主成分:

    2免费字体图像库由 Martin Solli 收集并上传(http://webstaff.itn.liu.se/~marso/)。

    from PIL import Image
    from numpy import *
    from pylab import *
    import pca
    
    im = array(Image.open(imlist[0])) # 打开一幅图像,获取其大小
    m,n = im.shape[0:2] # 获取图像的大小
    imnbr = len(imlist) # 获取图像的数目
    
    # 创建矩阵,保存所有压平后的图像数据
    immatrix = array([array(Image.open(im)).flatten()
                   for im in imlist],'f')
    
    # 执行 PCA 操作
    V,S,immean = pca.pca(immatrix)
    
    # 显示一些图像(均值图像和前 7 个模式)
    figure()
    gray()
    subplot(2,4,1)
    imshow(immean.reshape(m,n))
    for i in range(7):
      subplot(2,4,i+2)
      imshow(V[i].reshape(m,n))
    
    show()
    
    

    注意,图像需要从一维表示重新转换成二维图像;可以使用 reshape() 函数。如图 1-8 所示,运行该例子会在一个绘图窗口中显示 8 个图像。这里我们使用了 PyLab 库的 subplot() 函数在一个窗口中放置多个图像。

    图 1-8:平均图像(左上)和前 7 个模式(具有最大方差的方向模式)

    1.3.7 使用pickle模块

    如果想要保存一些结果或者数据以方便后续使用,Python 中的 pickle 模块非常有用。pickle模块可以接受几乎所有的 Python 对象,并且将其转换成字符串表示,该过程叫做封装(pickling)。从字符串表示中重构该对象,称为拆封(unpickling)。这些字符串表示可以方便地存储和传输。

    我们来看一个例子。假设想要保存上一节字体图像的平均图像和主成分,可以这样来完成:

    # 保存均值和主成分数据
    f = open('font_pca_modes.pkl', 'wb')
    pickle.dump(immean,f)
    pickle.dump(V,f)
    f.close()
    
    

    在上述例子中,许多对象可以保存到同一个文件中。pickle 模块中有很多不同的协议可以生成 .pkl 文件;如果不确定的话,最好以二进制文件的形式读取和写入。在其他 Python 会话中载入数据,只需要如下使用 load() 方法:

    # 载入均值和主成分数据
    f = open('font_pca_modes.pkl', 'rb')
    immean = pickle.load(f)
    V = pickle.load(f)
    f.close()
    
    

    注意,载入对象的顺序必须和先前保存的一样。Python 中有个用 C 语言写的优化版本,叫做cpickle 模块,该模块和标准 pickle 模块完全兼容。关于 pickle 模块的更多内容,参见pickle 模块文档页 http://docs.python.org/library/pickle.html

    在本书接下来的章节中,我们将使用 with 语句处理文件的读写操作。这是 Python 2.5 引入的思想,可以自动打开和关闭文件(即使在文件打开时发生错误)。下面的例子使用 with() 来实现保存和载入操作:

    # 打开文件并保存
    with open('font_pca_modes.pkl', 'wb') as f:
      pickle.dump(immean,f)
      pickle.dump(V,f)
    
    

    # 打开文件并载入
    with open('font_pca_modes.pkl', 'rb') as f:
      immean = pickle.load(f)
      V = pickle.load(f)
    
    

    上面的例子乍看起来可能很奇怪,但 with() 确实是个很有用的思想。如果你不喜欢它,可以使用之前的 open 和 close 函数。

    作为 pickle 的一种替代方式,NumPy 具有读写文本文件的简单函数。如果数据中不包含复杂的数据结构,比如在一幅图像上点击的点列表,NumPy 的读写函数会很有用。保存一个数组 x 到文件中,可以使用:

    savetxt('test.txt',x,'%i')
    
    

    最后一个参数表示应该使用整数格式。类似地,读取可以使用:

    x = loadtxt('test.txt')
    
    

    你可以从在线文档http://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html 了解更多内容。

    最后,NumPy 有专门用于保存和载入数组的函数。你可以在上面的在线文档里查看关于 save()和 load() 的更多内容。

    1.4 SciPy

    SciPyhttp://scipy.org/) 是建立在 NumPy 基础上,用于数值运算的开源工具包。SciPy 提供很多高效的操作,可以实现数值积分、优化、统计、信号处理,以及对我们来说最重要的图像处理功能。接下来,本节会介绍 SciPy 中大量有用的模块。SciPy 是个开源工具包,可以从http://scipy.org/Download 下载。

    1.4.1 图像模糊

    图像的高斯模糊是非常经典的图像卷积例子。本质上,图像模糊就是将(灰度)图像 I 和一个高斯核进行卷积操作:

    Iσ = I*Gσ

    其中 * 表示卷积操作;Gσ 是标准差为 σ 的二维高斯核,定义为 :

    G_\sigma=\frac{1}{2\pi\sigma^2}e^{-(x^2+y^2)/2\sigma^2}

    高斯模糊通常是其他图像处理操作的一部分,比如图像插值操作、兴趣点计算以及很多其他应用。

    SciPy 有用来做滤波操作的 scipy.ndimage.filters 模块。该模块使用快速一维分离的方式来计算卷积。你可以像下面这样来使用它:

    from PIL import Image
    from numpy import *
    from scipy.ndimage import filters
    
    im = array(Image.open('empire.jpg').convert('L'))
    im2 = filters.gaussian_filter(im,5)
    

    上面 guassian_filter() 函数的最后一个参数表示标准差。

    图 1-9 显示了随着 σ 的增加,一幅图像被模糊的程度。σ 越大,处理后的图像细节丢失越多。如果打算模糊一幅彩色图像,只需简单地对每一个颜色通道进行高斯模糊:

    im = array(Image.open('empire.jpg'))
    im2 = zeros(im.shape)
    for i in range(3):
      im2[:,:,i] = filters.gaussian_filter(im[:,:,i],5)
    im2 = uint8(im2)
    

    在上面的脚本中,最后并不总是需要将图像转换成 uint8 格式,这里只是将像素值用八位来表示。我们也可以使用:

    im2 = array(im2,'uint8')
    

    来完成转换。

    关于该模块更多的内容以及不同参数的选择,请查看http://docs.scipy.org/doc/scipy/reference/ndimage.html 上 SciPy 文档中的 scipy.ndimage部分。

    图 1-9:使用 scipy.ndimage.filters 模块进行高斯模糊:(a)原始灰度图像;(b)使用 σ=2 的高斯滤波器;(c)使用 σ=5 的高斯滤波器;(d)使用 σ=10 的高斯滤波器

    1.4.2 图像导数

    整本书中可以看到,在很多应用中图像强度的变化情况是非常重要的信息。强度的变化可以用灰度图像 I(对于彩色图像,通常对每个颜色通道分别计算导数)的 x 和 y 方向导数 Ix 和 Iy 进行描述。

    图像的梯度向量为∇I = [IxIy]T。梯度有两个重要的属性,一是梯度的大小

    \left|\boldsymbol{\nabla I}\right|=\sqrt{{\boldsymbol{I}_x}^2+{\boldsymbol{I}_y}^2}

    它描述了图像强度变化的强弱,一是梯度的角度

    α=arctan2(IyIx)

    描述了图像中在每个点(像素)上强度变化最大的方向。NumPy 中的 arctan2() 函数返回弧度表示的有符号角度,角度的变化区间为 -π...π。

    我们可以用离散近似的方式来计算图像的导数。图像导数大多数可以通过卷积简单地实现:

    Ix=I*Dx 和 Iy=I*Dy

    对于 Dx 和 Dy,通常选择 Prewitt 滤波器:

    D_x=\begin{vmatrix}-1&0&1\\-1&0&1\\ -1&0&1\end{vmatrix} 和 D_y=\begin{vmatrix}-1&-1&-1\\0&0&0\\ 1&1&1\end{vmatrix}

    或者 Sobel 滤波器:

    D_x=\begin{vmatrix}-1&0&1\\-2&0&2\\ -1&0&1\end{vmatrix} 和 D_y=\begin{vmatrix}-1&-2&-1\\0&0&0\\ 1&2&1\end{vmatrix}

    这些导数滤波器可以使用 scipy.ndimage.filters 模块的标准卷积操作来简单地实现,例如:

    from PIL import Image
    from numpy import *
    from scipy.ndimage import filters
    
    im = array(Image.open('empire.jpg').convert('L'))
    
    # Sobel 导数滤波器
    imx = zeros(im.shape)
    filters.sobel(im,1,imx)
    
    imy = zeros(im.shape)
    filters.sobel(im,0,imy)
    
    magnitude = sqrt(imx**2+imy**2)
    

    上面的脚本使用 Sobel 滤波器来计算 x 和 y 的方向导数,以及梯度大小。sobel() 函数的第二个参数表示选择 x 或者 y 方向导数,第三个参数保存输出的变量。图 1-10 显示了用 Sobel 滤波器计算出的导数图像。在两个导数图像中,正导数显示为亮的像素,负导数显示为暗的像素。灰色区域表示导数的值接近于零。

    图 1-10:使用 Sobel 导数滤波器计算导数图像:(a)原始灰度图像;(b)x 导数图像;(c)y导数图像;(d)梯度大小图像

    上述计算图像导数的方法有一些缺陷:在该方法中,滤波器的尺度需要随着图像分辨率的变化而变化。为了在图像噪声方面更稳健,以及在任意尺度上计算导数,我们可以使用高斯导数滤波器:

    Ix=I*Gσx 和 Iy=I*Gσy

    其中,Gσx和 Gσy 表示 Gσ 在 x 和 y 方向上的导数,Gσ 为标准差为 σ 的高斯函数。

    我们之前用于模糊的 filters.gaussian_filter() 函数可以接受额外的参数,用来计算高斯导数。可以简单地按照下面的方式来处理:

    sigma = 5 # 标准差
    
    imx = zeros(im.shape)
    filters.gaussian_filter(im, (sigma,sigma), (0,1), imx)
    
    imy = zeros(im.shape)
    filters.gaussian_filter(im, (sigma,sigma), (1,0), imy)
    

    该函数的第三个参数指定对每个方向计算哪种类型的导数,第二个参数为使用的标准差。你可以查看相应文档了解详情。图 1-11 显示了不同尺度下的导数图像和梯度大小。你可以和图 1-9 中做相同尺度模糊的图像做比较。

    图 1-11:使用高斯导数计算图像导数:x 导数图像(上),y 导数图像(中),以及梯度大小图像(下);(a)为原始灰度图像,(b)为使用 σ=2 的高斯导数滤波器处理后的图像,(c)为使 用 σ=5 的高斯导数滤波器处理后的图像,(d)为使用 σ=10 的高斯导数滤波器处理后的图像

    1.4.3 形态学:对象计数

    形态学(或数学形态学)是度量和分析基本形状的图像处理方法的基本框架与集合。形态学通常用于处理二值图像,但是也能够用于灰度图像。二值图像是指图像的每个像素只能取两个值,通常是 0 和 1。二值图像通常是,在计算物体的数目,或者度量其大小时,对一幅图像进行阈值化后的结果。你可以从 http://en.wikipedia.org/wiki/Mathematical_morphology 大体了解形态学及其处理图像的方式。

    scipy.ndimage 中的 morphology 模块可以实现形态学操作。你可以使用 scipy.ndimage 中的measurements 模块来实现二值图像的计数和度量功能。下面通过一个简单的例子介绍如何使用它们。

    考虑在图 1-12a3 里的二值图像,计算该图像中的对象个数可以通过下面的脚本实现:

    3这个图像实际上是图像“分割”后的结果。如果你想知道该图像是如何创建的,可以查看 9.3 节。

    from scipy.ndimage import measurements,morphology
    
    # 载入图像,然后使用阈值化操作,以保证处理的图像为二值图像
    im = array(Image.open('houses.png').convert('L'))
    im = 1*(im<128)
    
    labels, nbr_objects = measurements.label(im)
    print "Number of objects:", nbr_objects
    
    

    上面的脚本首先载入该图像,通过阈值化方式来确保该图像是二值图像。通过和 1 相乘,脚本将布尔数组转换成二进制表示。然后,我们使用 label() 函数寻找单个的物体,并且按照它们属于哪个对象将整数标签给像素赋值。图 1-12b 是 labels 数组的图像。图像的灰度值表示对象的标签。可以看到,在一些对象之间有一些小的连接。进行二进制开(binary open)操作,我们可以将其移除:

    # 形态学开操作更好地分离各个对象
    im_open = morphology.binary_opening(im,ones((9,5)),iterations=2)
    
    labels_open, nbr_objects_open = measurements.label(im_open)
    print "Number of objects:", nbr_objects_open
    

    binary_opening() 函数的第二个参数指定一个数组结构元素。该数组表示以一个像素为中心时,使用哪些相邻像素。在这种情况下,我们在 y 方向上使用 9 个像素(上面 4 个像素、像素本身、下面 4 个像素),在 x 方向上使用 5 个像素。你可以指定任意数组为结构元素,数组中的非零元素决定使用哪些相邻像素。参数 iterations 决定执行该操作的次数。你可以尝试使用不同的迭代次数 iterations 值,看一下对象的数目如何变化。你可以在图 1-12c 与图 1-12d 中查看经过开操作后的图像,以及相应的标签图像。正如你想象的一样,binary_closing() 函数实现相反的操作。我们将该函数和在 morphology 和 measurements 模块中的其他函数的用法留作练习。你可以从 scipy.ndimage 模块文档 http://docs.scipy.org/doc/scipy/reference/ndimage.html 中了解关于这些函数的更多知识。

    图 1-12:形态学示例。使用二值开操作将对象分开,然后计算物体的数目:(a)为原始二值图像;(b)为对应原始图像的标签图像,其中灰度值表示物体的标签;(c)为使用开操作后的二值图像;(d)为开操作后图像的标签图像

    1.4.4 一些有用的SciPy模块

    SciPy 中包含一些用于输入和输出的实用模块。下面介绍其中两个模块:io 和 misc

    1. 读写.mat文件

      如果你有一些数据,或者在网上下载到一些有趣的数据集,这些数据以 Matlab 的 .mat 文件格式存储,那么可以使用 scipy.io 模块进行读取。

      data = scipy.io.loadmat('test.mat')
      

      上面代码中,data 对象包含一个字典,字典中的键对应于保存在原始 .mat 文件中的变量名。由于这些变量是数组格式的,因此可以很方便地保存到 .mat 文件中。你仅需创建一个字典(其中要包含你想要保存的所有变量),然后使用 savemat() 函数:

      data = {}
      data['x'] = x
      scipy.io.savemat('test.mat',data)
      

      因为上面的脚本保存的是数组 x,所以当读入到 Matlab 中时,变量的名字仍为 x。关于scipy.io 模块的更多内容,请参见在线文档http://docs.scipy.org/doc/scipy/reference/io.html

    2. 以图像形式保存数组

      因为我们需要对图像进行操作,并且需要使用数组对象来做运算,所以将数组直接保存为图像文件 4 非常有用。本书中的很多图像都是这样的创建的。

      imsave() 函数可以从 scipy.misc 模块中载入。要将数组 im 保存到文件中,可以使用下面的命令:

      from scipy.misc import imsave
      imsave('test.jpg',im)
      

      scipy.misc 模块同样包含了著名的 Lena 测试图像:

      lena = scipy.misc.lena()
      

      该脚本返回一个 512×512 的灰度图像数组。

    4所有 Pylab 图均可保存为多种图像格式,方法是点击图像窗口中的“保存”按钮。

    1.5 高级示例:图像去噪

    我们通过一个非常实用的例子——图像的去噪——来结束本章。图像去噪是在去除图像噪声的同时,尽可能地保留图像细节和结构的处理技术。我们这里使用 ROF(Rudin-Osher-Fatemi)去噪模型。该模型最早出现在文献 [28] 中。图像去噪对于很多应用来说都非常重要;这些应用范围很广,小到让你的假期照片看起来更漂亮,大到提高卫星图像的质量。ROF 模型具有很好的性质:使处理后的图像更平滑,同时保持图像边缘和结构信息。

    ROF 模型的数学基础和处理技巧非常高深,不在本书讲述范围之内。在讲述如何基于 Chambolle 提出的算法 [5] 实现 ROF 求解器之前,本书首先简要介绍一下 ROF 模型。

    一幅(灰度)图像 I 的全变差(Total Variation,TV)定义为梯度范数之和。在连续表示的情况下,全变差表示为:

    J(\boldsymbol{I})=\int\left|\nabla\boldsymbol{I}\right|\text{dx}            (1.1)

    在离散表示的情况下,全变差表示为:

    J(\boldsymbol{I})=\sum_{\text{x}}\left|\nabla\boldsymbol{I}\right|

    其中,上面的式子是在所有图像坐标 x=[x, y] 上取和。

    在 Chambolle 提出的 ROF 模型里,目标函数为寻找降噪后的图像 U,使下式最小:

    \min_U\left|\left|\boldsymbol{I}-\boldsymbol{U}\right|\right|^2+2\lambda J(\boldsymbol{U}),

    其中范数 ||I-U|| 是去噪后图像 U 和原始图像 I 差异的度量。也就是说,本质上该模型使去噪后的图像像素值“平坦”变化,但是在图像区域的边缘上,允许去噪后的图像像素值“跳跃”变化。

    按照论文 [5] 中的算法,我们可以按照下面的代码实现 ROF 模型去噪:

    from numpy import *
    
    def denoise(im,U_init,tolerance=0.1,tau=0.125,tv_weight=100):
      """ 使用A. Chambolle(2005)在公式(11)中的计算步骤实现Rudin-Osher-Fatemi(ROF)去噪模型
    
        输入:含有噪声的输入图像(灰度图像)、U 的初始值、TV 正则项权值、步长、停业条件
    
        输出:去噪和去除纹理后的图像、纹理残留"""
    
      m,n = im.shape # 噪声图像的大小
    
      # 初始化
      U = U_init
      Px = im # 对偶域的x 分量
      Py = im # 对偶域的y 分量
      error = 1
    
    while (error > tolerance):
      Uold = U
    
      # 原始变量的梯度
      GradUx = roll(U,-1,axis=1)-U # 变量U 梯度的x 分量
      GradUy = roll(U,-1,axis=0)-U # 变量U 梯度的y 分量
    
      # 更新对偶变量
      PxNew = Px + (tau/tv_weight)*GradUx
      PyNew = Py + (tau/tv_weight)*GradUy
      NormNew = maximum(1,sqrt(PxNew**2+PyNew**2))
    
      Px = PxNew/NormNew # 更新x 分量(对偶)
      Py = PyNew/NormNew # 更新y 分量(对偶)
    
      # 更新原始变量
      RxPx = roll(Px,1,axis=1) # 对x 分量进行向右x 轴平移
      RyPy = roll(Py,1,axis=0) # 对y 分量进行向右y 轴平移
    
      DivP = (Px-RxPx)+(Py-RyPy) # 对偶域的散度
      U = im + tv_weight*DivP # 更新原始变量
    
      # 更新误差
      error = linalg.norm(U-Uold)/sqrt(n*m);
    
      return U,im-U # 去噪后的图像和纹理残余
    

    在这个例子中,我们使用了 roll() 函数。顾名思义,在一个坐标轴上,它循环“滚动”数组中的元素值。该函数可以非常方便地计算邻域元素的差异,比如这里的导数。我们还使用了linalg.norm() 函数,该函数可以衡量两个数组间(这个例子中是指图像矩阵 U和 Uold)的差异。我们将这个 denoise() 函数保存到 rof.py 文件中。

    下面使用一个合成的噪声图像示例来说明如何使用该函数:

    from numpy import *
    from numpy import random
    from scipy.ndimage import filters
    import rof
    
    # 使用噪声创建合成图像
    im = zeros((500,500))
    im[100:400,100:400] = 128
    im[200:300,200:300] = 255
    im = im + 30*random.standard_normal((500,500))
    
    U,T = rof.denoise(im,im)
    G = filters.gaussian_filter(im,10)
    
    # 保存生成结果
    from scipy.misc import imsave
    imsave('synth_rof.pdf',U)
    imsave('synth_gaussian.pdf',G)
    

    原始图像和图像的去噪结果如图 1-13 所示。正如你所看到的,ROF 算法去噪后的图像很好地保留了图像的边缘信息。

    图 1-13:使用 ROF 模型对合成图像去噪:(a)为原始噪声图像;(b)为经过高斯模糊的图像(σ=10);(c)为经过 ROF 模型去噪后的图像

    下面看一下在实际图像中使用 ROF 模型去噪的效果:

    from PIL import Image
    from pylab import *
    import rof
    
    im = array(Image.open('empire.jpg').convert('L'))
    U,T = rof.denoise(im,im)
    
    figure()
    gray()
    imshow(U)
    axis('equal')
    axis('off')
    show()
    

    经过 ROF 去噪后的图像如图 1-14c 所示。为了方便比较,该图中同样显示了模糊后的图像。可以看到,ROF 去噪后的图像保留了边缘和图像的结构信息,同时模糊了“噪声”。

    图 1-14:使用 ROF 模型对灰度图像去噪:(a)为原始噪声图像;(b)为经过高斯模糊的图像(σ=5);(c)为经过 ROF 模型去噪后的图像

    练习

    1. 如图 1-9 所示,将一幅图像进行高斯模糊处理。随着 σ 的增加,绘制出图像轮廓。在你绘制出的图中,图像的轮廓有何变化?你能解释为什么会发生这些变化吗?

    2. 通过将图像模糊化,然后从原始图像中减去模糊图像,来实现反锐化图像掩模操作(http://en.wikipedia.org/wiki/Unsharp_masking)。反锐化图像掩模操作可以实现图像锐化效果。试在彩色和灰度图像上使用反锐化图像掩模操作,观察该操作的效果。

    3. 除了直方图均衡化,商图像是另一种图像归一化的方法。商图像可以通过除以模糊后的图像I/(IGσ) 获得。尝试使用该方法,并使用一些样本图像进行验证。

    4. 使用图像梯度,编写一个在图像中获得简单物体(例如,白色背景中的正方形)轮廓的函数。

    5. 使用梯度方向和大小检测图像中的线段。估计线段的长度以及线段的参数,并在原始图像中重新绘制该线段。

    6. 使用 label() 函数处理二值化图像,并使用直方图和标签图像绘制图像中物体的大小分布。

    7. 使用形态学操作处理阈值化图像。在发现一些参数能够产生好的结果后,使用 morphology 模块里面的 center_of_mass() 函数寻找每个物体的中心坐标,将其在图像中绘制出来。

    代码示例约定

    从第 2 章起,我们假定 PIL、NumPy 和 Matplotlib 都包括在你所创建的每个文件和每个代码例子的开头:

    from PIL import Image
    from numpy import *
    from pylab import *
    

    这种约定使得示例代码更清晰,同时也便于读者理解。除此之外,我们使用 SciPy 模块时,将会在代码示例中显式声明。

    一些纯化论者会反对这种将全体模块导入的方式,坚持如下使用方式:

    import numpy as np
    import matplotlib.pyplot as plt
    

    这种方式能够保持命名空间(知道每个函数从哪儿来)。因为我们不需要 PyLab 中的 NumPy 部分,所以该例子只从 Matplotlib 中导入 pyplot 部分。纯化论者和经验丰富的程序员们知道这些区别,他们能够选择自己喜欢的方式。但是,为了使本书的内容和例子更容易被读者接受,我们不打算这样做。

    请读者注意。


    展开全文
  • Python图像处理

    2020-10-25 19:22:28
    [Python图像处理] 一.图像处理基础知识及OpenCV入门函数 [Python图像处理] 二.OpenCV+Numpy库读取与修改像素 [Python图像处理] 三.获取图像属性、兴趣ROI区域及通道处理 [Python图像处理] 四.图像平滑之均值滤波、...
  • 本篇文章主要讲解基于理论的图像分割方法,通过K-Means聚类算法实现图像分割或颜色分层处理。基础性文章,希望对你有所帮助。 1.K-Means原理 2.K-Means聚类分割灰度图像 3.K-Means聚类对比分割彩色图像

空空如也

1 2 3 4 5 ... 20
收藏数 14,227
精华内容 5,690
关键字:

python图像处理

python 订阅