Python 基于 OpenCV 视觉图像处理实战 之 图像相关的基本概念,以及图像的基础操作 二
目录
Python 基于 OpenCV 视觉图像处理实战 之 图像相关的基本概念,以及图像的基础操作 二
一、简单介绍
二、图像的几何变换
三、插值算法
1、最近邻插值算法
2、双线性插值算法
3、插值算法的实现
四、图像的缩放
1、知识介绍
2、简单案例:图像的缩放
五、图像的平移
1、知识介绍
2、简单案例:图像平移
六、图像的旋转
1、知识介绍
2、简单案例:图像的旋转
七、图像的镜像变换
1、知识介绍
2、简单案例:图像镜像变换
八、图像色彩空间基础知识
1、图像的色调、色相、饱和度、亮度和对比度
2、RGB色彩空间
3、HSV色彩空间
4、HSI色彩空间
九、图像的直方图
1、图像直方图的基本概念
2、绘制灰度图像的直方图
3、绘制彩色图像的直方图
4、图像直方图均衡化
5、图像直方图反向投影
一、简单介绍
Python是一种跨平台的计算机程序设计语言。是一种面向对象的动态类型语言,最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。Python是一种解释型脚本语言,可以应用于以下领域: Web 和 Internet开发、科学计算和统计、人工智能、教育、桌面界面开发、软件开发、后端开发、网络爬虫。
这里使用 Python 基于 OpenCV 进行视觉图像处理,......
二、图像的几何变换
图像的几何变换是指在不改变图像内容的前提下对图像的像素进行空间几何变换,主要包括图像的平移变换、镜像变换、缩放和旋转等。
一个几何变换需要两部分运算:
- 第一部分是求解空间变换坐标函数,用以描述平移、缩放、旋转和镜像等变换后输出图像与输入图像之间的像素映射关系;
- 第二部分是插值运算,因为按照这种变换关系进行计算时,输出图像的像素可能被映射到输入图像的非整数坐标上。
设原图像f(x0,y0)经过几何变换产生的目标图像为g(x1,y1),则该空间变换(映射)关系可表示为:
x1=s(x0,y0)
y1=t(x0,y0)
其中,s(x0,y0)和t(x0,y0)为由f(x0,y0)到g(x1,y1)的坐标变换函数。
求解出坐标变换函数后开始计算变换后的图像像素位置,然后插值到变换后的图像上。
三、插值算法
对于数字图像而言,像素的坐标是离散型非负整数,在进行空间坐标变换计算的过程中,有可能产生浮点坐标值。
例如,原图像坐标(11,11)在缩放过程中缩小到一半的时候,坐标变成了(5.5,5.5),这种坐标值是无效的。
插值算法就是用来处理这些浮点坐标的。
常见的插值算法有最邻近插值法、双线性插值法、二次立方插值法、三次立方插值法等。
其中最常见的是 最邻近插值 和 双线性插值,接下来会具体介绍这两种插值算法。
1、最近邻插值算法
这是最简单的一种插值方法。在坐标(i,j)的四邻域空间中,按照坐标将邻域划分成四个范围,分别标记为A、B、C、D,将计算出来的像素坐标放到这个邻域中,看坐标在哪个区域就将距离最近的邻接像素灰度值赋予它,如下图
设u、v为大于0小于1的小数,(i+u,j+v)为输入的像素坐标,则待求像素灰度值为f(i+u,j+v),根据u、v值的不同,f(i+u,j+v)的值也不同,具体规则如下:
如果u<0.5,v<0.5,则(i+u,j+v)落在A区,f(i+u,j+v)=f(i,j);
如果u>=0.5,v<0.5,则(i+u,j+v)落在B区,f(i+u,j+v)=f(i+1,j);
如果u<0.5,v>0.5,则(i+u,j+v)落在C区,f(i+u,j+v)=f(i,j+1);
如果u>0.5,v>=0.5,则(i+u,j+v)落在D区,f(i+u,j+v)=f(i+1,j+1)。
从上述算法中可以看出,最近邻插值的计算量很小,但是不可避免地造成了图像灰度上的不连续,在灰度变化密集的地方很容易出现锯齿。
2、双线性插值算法
同样,对于一个目标像素,设置坐标通过反向变换得到的浮点坐标为(i+u,j+v),其中i、j均为非负整数,u、v为[0,1)区间的浮点数,则这个像素的值f(i+u,j+v)可由原图像中坐标为(i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所对应的周围4个像素的值来决定,即
f(i+u,j+v)=(1-u)(1-v)f(i,j)+(1-u)vf(i,j+1)+u(1-v)f(i+1,j)+uvf(i+1,j+1)
其中,f(i,j)表示源图像(i,j)处的的像素值。
跟最近邻插值算法相比,双线性插值计算量大,但缩放后图像质量高,不会出现像素值不连续的情况。但是双线性插值具有低通过滤器的特性,在插值计算中可能会损失图像高频分量,使图像轮廓在一定程度上变得模糊。
3、插值算法的实现
根据上面的算法规则描述,插值算法可以自己实现的难度不大,在图像处理过程中插值经常配合其他操作使用,因此在OpenCV很多函数里面集成了插值的实现,比如OpenCV函数resize(),里面已经集成了大量的插值算法。
resize()函数可以实现图像大小变换,默认插值方法为双线性插值。函数声明如下:
dst=cv2.resize(src, dsize, fx, fy, interpolation)
参数说明:
- src:输入图像。
- dst:输出图像。
- dsize:输出图像的大小。如果这个参数不为0,那么就代表将原图像缩放到这个Size(width,height)指定的大小;如果这个参数为0,那么原图像缩放之后的大小就要通过公式来计算,dsize=Size(round(fx*src.cols),round(fy*src.rows))。其中,fx和fy是图像Width方向和Height方向的缩放比例。
- fx:Width方向的缩放比例,如果是0,那么就会按照dsize.width/src.cols来计算。
- fy:Height方向的缩放比例,如果是0,那么就会按照dsize.height/src.rows来计算。
- interpolation:表示插值方式。
四、图像的缩放
1、知识介绍
图像的缩放主要用于改变图像的大小。缩放后,图像的宽度和高度会发生变化。图像缩放包含两个系数:水平缩放系数和垂直缩放系数。
水平缩放系数控制图像宽度的缩放,其值为1,则图像的宽度不变;其值小于1,则图像的宽度变窄;其值大于1,则图像的宽度变宽。垂直缩放系数控制图像高度的缩放,其值为1,则图像的高度不变;其值小于1,则图像的宽度变矮;其值大于1,则图像的宽度变高。如果水平缩放系数和垂直缩放系数不相等,那么缩放后图像的宽度和高度的比例会发生变化,使图像变形。要保持图像宽度和高度的比例不发生变化,就需要水平缩放系数和垂直缩放系数相等。
缩放原理
设水平缩放系数为fx,垂直缩放系数为fy,(x0,y0)为缩放前的坐标,(x,y)为缩放后的坐标,其缩放的坐标映射关系如下:
x=x0×fx
y=y0×fy
注意:图像在放大的时候调用了插值算法,放大的图片会出现一些失真,尤其在放大倍数很大的时候会出现模糊的现象,但是缩小后作为下采样则不会出现上述问题。
2、简单案例:图像的缩放
1)编写代码
2)运行效果
3)具体代码
"""
图像的缩放1)输入的原图,2)调用了resize()函数,3)一张图片是整体尺寸缩小到了100×100,4)一张图片是等比例放大2倍。5)resize()函数既可以指定输出图像的具体尺寸,也可以指定图像水平或垂直缩放的比例。
"""import cv2def main():img = cv2.imread("Images/Dog.jpg")# 图片尺寸height, width, n = img.shape# 缩小downscale = cv2.resize(img, (100, 100), interpolation=cv2.INTER_LINEAR)# 放大upscale = cv2.resize(img, (2 * width, 2 * height), interpolation=cv2.INTER_LINEAR)# 设置窗口属性,并显示图片cv2.namedWindow("downscale", cv2.WINDOW_KEEPRATIO)cv2.imshow("downscale", downscale)# 设置窗口属性,并显示图片cv2.namedWindow("upscale", cv2.WINDOW_KEEPRATIO)cv2.imshow("upscale", upscale)# 设置窗口属性,并显示图片cv2.namedWindow("Dog", cv2.WINDOW_KEEPRATIO)cv2.imshow("Dog", img)cv2.waitKey(0)if __name__ == '__main__':main()
五、图像的平移
1、知识介绍
图像的平移变换就是将图像所有的像素坐标分别加上指定的水平偏移量和垂直偏移量。
tx、ty分别代表水平和垂直方向上平移的距离。
这里利用np.array()创建这个矩阵,然后调用warpAffine来实现这个变换并保持图像大小不变。
OpenCV的warpAffine()函数声明如下:
dst=cv2. warpAffine( src, M, dsize)
参数说明:
- src ---- 代表图像 。
- M ---- 代表一个2行3列的矩阵,根据此矩阵的值变换原图中像素的位置 。
- dsize ----代表输出图像的尺寸大小 。
- dst ---- 代表经过仿射变换后输出的图像。
平移是让图像中所有像素同时沿着水平或垂直方向移动。在平移的操作中,我们需要把M设置为以下格式实现平移操作。
M = [[1, 0, 水平移动的距离], [0, 1, 垂直移动的距离]]
若水平移动的距离为正数,图像会向右移动,若为负数,图像会向左移动;
若垂直移动的距离为正数,图像会向下移动,若为负数,图像会向上移动。
2、简单案例:图像平移
1)编写代码
2)运行效果
代码中,图片的平移并没有设置改变图像的尺寸,因此平移后无像素的地方显示为黑色。
3)代码
"""
图像平移1)首先构建变换矩阵M,设置向左、向下各平移50个像素,则M=[[1,0,-100],[0,1,100]。2)图片的平移并没有设置改变图像的尺寸,因此平移后无像素的地方显示为黑色。
"""import numpy as np
import cv2def main():img = cv2.imread('Images/Dog.jpg')height, width, n = img.shapeM = np.array([[1, 0, 100], [0, 1, 100]], np.float32)# 图像平移img_tr = cv2.warpAffine(img, M, img.shape[:2])# 设置窗口属性,并显示图片cv2.namedWindow("img_tr", cv2.WINDOW_KEEPRATIO)cv2.imshow('img_tr', img_tr)# 设置窗口属性,并显示图片cv2.namedWindow("Dog", cv2.WINDOW_KEEPRATIO)cv2.imshow('Dog', img)cv2.waitKey(0)if __name__ == '__main__':main()
六、图像的旋转
1、知识介绍
图像的旋转就是让图像按照某一点旋转到指定的角度。
图像旋转后不会变形,但是其垂直对称轴和水平对称轴都会发生改变,旋转后图像的坐标和原图像坐标之间的关系已不能通过简单的加减乘除来得到,而需要通过一系列的复杂运算得到。
需要确定3个参数:图像的旋转中心点、旋转角度和缩放因子。
OpenCV中集成了getRotationMatrix2D()函数来实现图像的旋转,具体函数声明如下:
M = cv2.getRotationMatrix2D(center, angle, scale)
参数说明:
- center ---- 代表旋转的中心点坐标。
- angle ---- 代表旋转的角度,正数表示逆时针旋转,负数表示顺时针旋转。
- scale ----代表缩放比例,浮点类型。
- M ---- 代表计算出的仿射矩阵。
调用getRotationMatrix2D()函数之后得到了图像的变换矩阵M,然后再调用warpAffine()函数,输入图像变换矩阵M得到最终的结果
注意:图像在旋转和平移后都会出现图像被裁剪的问题。对于这种情况,需要先计算输出图像的尺寸,然后调整参数,即可避免这个问题。
2、简单案例:图像的旋转
1)编写代码
2)运行结果
旋转之后依旧不改变图像的尺寸,所以会出现图像信息丢失的情况
3)具体代码
"""
图像旋转
"""import cv2def main():img = cv2.imread("Images/Dog.jpg")height, width, n = img.shape# 中心旋转 90°M = cv2.getRotationMatrix2D((width / 2, height / 2), 90, 1)img_rotatin = cv2.warpAffine(img, M, img.shape[:2])# 设置窗口属性,并显示图片cv2.namedWindow("img_rotatin", cv2.WINDOW_KEEPRATIO)cv2.imshow('img_rotatin', img_rotatin)# 设置窗口属性,并显示图片cv2.namedWindow("Dog", cv2.WINDOW_KEEPRATIO)cv2.imshow('Dog', img)cv2.waitKey(0)if __name__ == '__main__':main()
七、图像的镜像变换
1、知识介绍
图像的镜像变换分为两种:水平镜像和垂直镜像。
水平镜像以图像垂直中线为轴,将图像的像素进行对换,也就是将图像的左半部和右半部对调。
垂直镜像则是以图像的水平中线为轴,将图像的上半部分和下半部分对调。
镜像变换原理
设输入图像的变换坐标为(x0,y0),变换后的坐标为(x,y),Width是图像的宽,Height为图像的高,因此可以得出:
- 水平镜像变换:x=width-x0-1,y=y0;
- 垂直镜像变换:x=x0,y=height-y0-1。
OpenCV中集成了直接实现镜像变换的flip()函数,函数声明如下:
dst=cv2.flip( src, flipCode, dst=None)
参数说明:
- src:输入图像。
- flipCode:翻转模式,flipCode==0垂直翻转(沿x翻转),flipCode>0水平翻转(沿y翻转),flipCode<0水平垂直翻转(先沿x轴翻转,再沿y翻转,等价于旋转180°)。
- 输出为变换后的图像。
2、简单案例:图像镜像变换
1)编写代码
2)运行结果
3)具体代码
"""
图像镜像变换
"""import cv2def main():img = cv2.imread("Images/Dog.jpg")# 水平翻转xImg = cv2.flip(img, 1, dst=None)# 垂直翻转yImg = cv2.flip(img, 0, dst=None)# 设置窗口属性,并显示图片cv2.namedWindow("xImg Horizontal", cv2.WINDOW_KEEPRATIO)cv2.imshow("xImg Horizontal", xImg)# 设置窗口属性,并显示图片cv2.namedWindow("yImg Vertical", cv2.WINDOW_KEEPRATIO)cv2.imshow("yImg Vertical", yImg)# 设置窗口属性,并显示图片cv2.namedWindow("Dog Origin", cv2.WINDOW_KEEPRATIO)cv2.imshow("Dog Origin", img)cv2.waitKey(0)if __name__ == '__main__':main()
八、图像色彩空间基础知识
在计算机视觉和图像处理领域,色彩空间指的是组织色彩的特定方式,是进行颜色信息研究的理论基础,它将颜色从人们的主观感受量化为具体的表达,为用计算机来记录和表现颜色提供了有力的依据。
一幅图像可以用不同的色彩空间表示,有很多很有用的不同的颜色空间。
其中,一些常见的颜色空间有RGB、HSI、HSV和HSB等。不同的颜色空间有不同的优点。
接下来将介绍几种常见的色彩空间描述,以及图像的颜色参数,如色调、色相、饱和度、对比度和亮度等。
1、图像的色调、色相、饱和度、亮度和对比度
1)色调 (Hue)
色调是指色彩外观的基本倾向,描述了图像色彩模式下原色的明暗程度,范围为0~255,共256级色调。对于灰度图像,当色调级别为255时就是白色,当级别为0时就是黑色,中间是各种程度不同级别的灰色。在RGB色彩空间下,色调代表红、绿、蓝三种原色的明暗程度,而红色有淡红色、玫红色、深红色、暗红色等不同的色调。
2)色相 (Hue)
色调是色彩的基本属性之一,它描述了我们所感知到的颜色的种类或者色系。比如,从红到紫的不同色调代表了不同的颜色类别,它们都在色谱中占据着不同的位置。从艳丽的橙色到深邃的蓝色,色调的变化带来了丰富多彩的视觉体验。
3)饱和度 (Saturation)
饱和度定义了颜色的纯度或者强度。当一种颜色完全饱和时,它呈现出鲜艳、浓郁的外观,没有任何混合或者灰色调。相反,当饱和度较低时,颜色变得柔和、淡雅,甚至可能变成灰色或无色。
4)亮度 (Brightness)
亮度是指颜色的相对明暗程度。它决定了我们感知到的颜色是否看起来明亮或暗淡。比如,一种明亮的黄色会比同样的颜色的低亮度版本更容易引起注意,因为它更加醒目和生动。
5)对比度 (Contrast)
对比度是描述图像中不同区域之间亮度或颜色的差异程度。高对比度意味着图像中的明暗或颜色差异很大,使得物体轮廓清晰,细节丰富,色彩生动。而低对比度则使得图像显得柔和,细节较少,色彩过渡平滑。
2、RGB色彩空间
RGB色彩空间是最常见的颜色空间。R、G、B分别代表红色(Red)、绿色(Green)、蓝色(Blue)。在这个颜色空间中,每一种颜色都由R、G、B的不同权重代表。在几何上,以R、G、B三个互相垂直的轴所构成的空间坐标系被称为RGB模型。RGB色彩系统用R、G、B三原色通过不同比例的混合来表示任一种色彩,其优点是直观、易于理解。
但是三个颜色分量之间是高度相关的,如果一个颜色的某一个分量发生了一定程度的改变,那么这个颜色很可能也要发生改变。例如,如果改变图像的亮度,那么RGB的3个分量都会相应地改变。
3、HSV色彩空间
HSV(Hue,Saturation,Value)色彩空间对应于画家配色的方法。画家用改变色浓和色深的方法从某种纯色获得不同色调的颜色,在一种纯色中加入白色以改变色浓,加入黑色以改变色深,同时加入不同比例的白色和黑色,即可获得各种不同的色调。
在几何上用圆柱坐标系中的一个圆锥形子集来描述HSV模型,圆锥的顶面对应于V=1,它包含RGB模型中的R=1、G=1、B=1这3个面。色彩H由绕V轴的旋转角给定。红色对应于角度0°,绿色对应于角度120°,蓝色对应于角度240°。
HSV模型中的V轴对应于RGB颜色空间中的主对角线。在圆锥顶面的圆周上的颜色,V=1、S=1这种颜色是纯色。
4、HSI色彩空间
HIS(Hue,Intensity,Saturation)色彩空间从视觉感官出发,用色调(Hue)、饱和度(Saturation)和亮度(Intensity)来描述颜色。HIS空间可以用圆锥空间模型来描述。其中,色调H由角度表示,取值范围为0~360°,每隔60°表示一种基本颜色(其他度数是相邻的基本度数之间的颜色):红(RGB(255,0,0))→黄(RGB(255,255,0))→绿(RGB(0,255,0))→青(RGB(0,255,255))→蓝(RGB(0,0,255))→紫(RGB(255,0,255))→红。
饱和度S是HIS彩色空间中轴线到彩色点的半径长度,彩色点离轴线的距离越近,表示颜色的白光越多。强度I用轴线方向上的高度表示,圆锥体的轴线描述了灰度级,强度最小值时为黑色,强度最大值时为白色。每个和轴线正交的切面上的点,其强度值都是相等的。
5、简单案例:调整图片亮度对比度
代码:
"""
调整图片亮度对比度
"""import cv2
import numpy as np# 修改图像的对比度,coefficent>0, <1降低对比度,>1提升对比度 建议0-2
def change_contrast(img, coefficent):imggray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)m = cv2.mean(img)[0]graynew = m + coefficent * (imggray - m)img1 = np.zeros(img.shape, np.float32)k = np.divide(graynew, imggray, out=np.zeros_like(graynew), where=imggray != 0)img1[:, :, 0] = img[:, :, 0] * kimg1[:, :, 1] = img[:, :, 1] * kimg1[:, :, 2] = img[:, :, 2] * kimg1[img1 > 255] = 255img1[img1 < 0] = 0return img1.astype(np.uint8)# 修改图像的亮度,brightness取值0~2 <1表示变暗 >1表示变亮
def change_brightness(img, brightness):[averB, averG, averR] = np.array(cv2.mean(img))[:-1] / 3k = np.ones((img.shape))k[:, :, 0] *= averBk[:, :, 1] *= averGk[:, :, 2] *= averRimg = img + (brightness - 1) * kimg[img > 255] = 255img[img < 0] = 0return img.astype(np.uint8)def cvshow(name, img):cv2.namedWindow(name, cv2.WINDOW_NORMAL)cv2.resizeWindow(name, 1280, 720)cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyWindow(name)def main():# 加载图片 读取彩色图像image = cv2.imread('Images/Dog.jpg', cv2.IMREAD_COLOR)l = 50c = 50MAX_VALUE = 100# 调节饱和度和亮度的窗口cv2.namedWindow("changImage", cv2.WINDOW_AUTOSIZE)def nothing(*arg):pass# 滑动块cv2.createTrackbar("l", "changImage", l, MAX_VALUE, nothing)cv2.createTrackbar("c", "changImage", c, MAX_VALUE, nothing)while True:# 得到 l、 s 、c的值l = cv2.getTrackbarPos('l', "changImage")c = cv2.getTrackbarPos('c', "changImage")img = np.copy(image.astype(np.float32))# 亮度 -1~1img = change_brightness(img, float(l - 50) / float(50))# 对比度 0~2img = change_contrast(img, c / 50)# 显示调整后的效果img = cv2.resize(img, (1280, 720))cv2.imshow("changImage", img)ch = cv2.waitKey(5)# 按 ESC 键退出if ch == 27:break# 关闭所有的窗口cv2.destroyAllWindows()if __name__ == "__main__":main()
九、图像的直方图
直方图应用于统计学中。一系列高度不等的线段用来描述数据分布。在图像处理中直方图的意义很大,经常用来统计不同颜色的分布情况等。
另外,对直方图的一些变换可以改变图像的一些特性,比如直方图均衡化操作。
1、图像直方图的基本概念
图像直方图有两个参数:bins和range。bins表示特征统计量。例如,在图像直方图中,可以把一个灰度值设置为一个bin,0~255强度的灰度值一共就需要256个bin。range表示一个bins能够达到的最大和最小的范围。例如,一张10×10的图片,如果直方图是按照亮度统计像素数量,那么range的范围就是0~100。
在图像处理中,bins不仅是指灰度值,而且它作为直方图的统计数据参量,可能是任何能有效描述图像的特征,比如梯度、方向、色彩或任何其他特征等。
一张用以表示数字图像中亮度分布的直方图,描绘了图像中亮度值的像素数。可以借助观察该直方图了解如何调整亮度分布,直方图中横坐标的左侧为纯黑或较暗的区域,右侧为纯白较亮的区域。因此,对于一幅较暗的图片的图像直方图,其数据多集中于左侧和中间部分,而整体明亮、只有少量阴影的图像直方图中则集中于右侧部分。直方图的意义如下:
- 直方图是图像中像素强度分布的图形表达方式。
- 直方图统计了每个强度值所具有的像素个数。
2、绘制灰度图像的直方图
1)在Python中,图像的直方图绘制需要用到Matplotlib库,它是一个Python的2D绘图库。
在环境中,pip install matplotlib 安装即可
安装成功后,开始绘制图像的直方图,其中用到了OpenCV的alcHist()函数,其主要功能是计算图像直方图,函数原型如下:
hist=cv2.calcHist(images, channels, mask, histSize, ranges, hist=None,
accumulate=None)
参数说明:
- images:输入图像,传入时应该用中括号[]括起来。
- channels:传入图像的通道,如果是灰度图像,那么只有一个通道,值为0;如果是彩色图像(有3个通道),那么在0、1、2中选择一个值,对应着BGR的各个通道,这个值也得用[]传入。
- mask:掩膜图像,如果统计整幅图,那么为None;如果要统计部分图的直方图,就得构造相应的掩膜来计算。
- histSize:灰度级的个数,需要中括号,比如[256]。
- range:像素值的范围,通常为[0,256]。此外,假如channels为[0,1],ranges为[0,256,0,180],则代表0通道范围是0~256,1通道范围0~180。
- 输出为计算出来的直方图。
2)灰度图像直方图的绘制
按照一张输入图像的灰度格式,x坐标代表0~255级的bins,y坐标代表不同bins下的像素个数。从下图中可以看出,该图像在低灰度区域和高灰度区域中的像素分布比较多。
代码:
"""
绘制灰度图像直方图1)按照一张输入图像的灰度格式,x坐标代表0~255级的bins,y坐标代表不同bins下的像素个数。
"""import cv2
from matplotlib import pyplot as pltdef main():img = cv2.imread("Images/Gray_Dog.jpg")# 设置窗口属性,并显示图片cv2.namedWindow("Dog", cv2.WINDOW_KEEPRATIO)cv2.imshow("Dog", img)hist = cv2.calcHist([img], [0], None, [256], [0, 256])# 新建一个图像plt.figure()# 图坐标图标题plt.title("Grayscale Histogram")# 图像 x 轴标签plt.xlabel("Bins")# 图像 y 轴标签plt.ylabel("# of Pixels")# 画图plt.plot(hist)# 设置 x 轴 的坐标范围plt.xlim([0, 256])# 显示图坐标plt.show()# input()cv2.waitKey()# 关闭所有的窗口cv2.destroyAllWindows()if __name__ == '__main__':main()
3、绘制彩色图像的直方图
灰度图像只有一个通道,因此绘制比较简单。
对于彩色图像,它需要用到之前的split()函数将图像通道分开,然后进行绘制。
R、G、B三种颜色都绘制在同一张直方图上,可以看出相同bins下不同颜色的数目分布
代码:
"""
彩色图像直方1)R、G、B三种颜色都绘制在同一张直方图上,可以看出相同bins下不同颜色的数目分布
"""import cv2
from matplotlib import pyplot as pltdef main():img = cv2.imread("Images/Dog.jpg")# 设置窗口属性,并显示图片cv2.namedWindow("Dog", cv2.WINDOW_KEEPRATIO)cv2.imshow("Dog", img)chans = cv2.split(img)colors = ('b', 'g', 'r')# 新建一个图像plt.figure()# 图像的标题plt.title(" Flattened Color Hisogram")# x 轴标签plt.xlabel("Bins")# y 轴标签plt.xlabel("# of Pixels")for (chan, color) in zip(chans, colors):hist = cv2.calcHist([chan], [0], None, [256], [0, 256])plt.plot(hist, color=color)plt.xlim([0, 256])# 显示图像plt.show()# input()cv2.waitKey()# 关闭所有的窗口cv2.destroyAllWindows()if __name__ == '__main__':main()
4、图像直方图均衡化
图像的直方图展示了图像不同灰度级别的像素分布。
从前面绘制的直方图中可以看出,图像的像素分布很不均衡。在图像处理中,如果一幅图像的像素占有很多的灰度级而且分布均匀,那么这样的图像往往有高对比度。直方图均衡化就是调整输入图像的直方图信息,使得图像像素在灰度级别上均衡分布的的一种图像变化方法。
直方图均衡化的基本思想是:
对图像中像素个数多的灰度级进行展宽,而对图像中像素个数少的灰度进行压缩,从而扩展图像像素取值的动态范围。
图像均衡化的作用是提高对比度和灰度色调的变化,使图像更加清晰。
OpenCV的equalizeHist()函数,该函数功能是实现直方图均衡化,其原型如下:
dst=equalizeHist(src)
参数说明:
- src:输入图像。
- 输出为图像均衡化后的图像。
注意:直方图均衡化对图像对比度增强的效果非常明显,原本图像中亮的地方亮度增加,图像暗的地方更加暗。但是图像均衡化并不适用于所有的场景,对于一些输入图像,均衡化高亮的部分会出现光斑,视觉效果很差。
灰度图像直方图均衡化示例,运行效果如下
代码:
"""
灰度图像直方图均衡化(全局)
"""import cv2
import numpy as npdef main():# 读取图片并且灰度化img = cv2.imread("Images/Cullet.jpg", 0)# 图像均衡化eq = cv2.equalizeHist(img)# 设置窗口属性,并显示图片cv2.namedWindow("Histogram Equalization", cv2.WINDOW_KEEPRATIO)cv2.imshow("Histogram Equalization", np.hstack([img, eq]))cv2.waitKey(0)if __name__ == "__main__":main()
以上展示的是图片的全局直方图均衡化,也就是对整张图像都进行了处理。
在实际项目中,有的时候这种操作并不是很好,会把某些不该调整的部分调整了,而我们需要的可能只是对图像的某一块区域进行均衡化处理。
OpenCV中还有一种局部直方图均衡化函数,也就是说把整个图像分成许多小块(如按8×8把图像分成多个小块),再对每个小块进行均衡化。
调用了OpenCV的createCLAHE()函数,其声明如下:
clahe=createCLAHE([, clipLimit[, tileGridSize]])
参数说明:
- clipLimit:对比度的大小。
- tileGridSize:每次处理块的大小。
灰度图像直方图局部均衡化处理,运行效果如下:
如图所示,可以看出,提升的对比度没有之前明显,程序的主要功能是把图片切分成了几个8×8的小块,并对每个小块里面进行直方图均衡化。
代码:
"""
灰度图像直方图局部均衡化
"""import cv2
import numpy as npdef main():img = cv2.imread("Images/Cullet.jpg", 0)# 创建一个 8x8 的 clashclahe = cv2.createCLAHE(5, (8, 8))# 图片切分成 8x8 的小块dst = clahe.apply(img)# 设置窗口属性,并显示图片cv2.namedWindow("Local Histogram Equalization", cv2.WINDOW_KEEPRATIO)cv2.imshow("Local Histogram Equalization", np.hstack([img, dst]))cv2.waitKey(0)if __name__ == "__main__":main()
5、图像直方图反向投影
图像直方图的反向投影定义是通过直方图来生成图像,反向投影生成图在某一位置的像素值就是原图对应位置的像素值在原图像中的总数目。
反向投影的实现过程比图像计算直方图的过程容易理解,就是统计图像中像素分布的概率,而反向投影正好相反,是通过直方图来形成图像。在由反向投影生成的图像中,如某像素值在直方图中的值越大,在进行反向投影操作时其对应的像素值越大,如某灰度值所占面积越小,其反向投影后像素值就会更小。
OpenCV中定义了calcBackProject()函数用来计算直方图反向投影,函数原型如下:
st=calcBackProject(images, channels, hist, ranges, scale[, dst])
参数说明:
- images:输入图像(HSV图像)。
- channels:用于计算反向投影的通道列表,通道数必须与直方图维度相匹配。
- hist:输入的模板图像直方图。
- ranges:直方图中每个维度bin的取值范围(即每个维度有多少个bin)。
- scale:可选输出反向投影的比例因子,一般取1。
首先读入两幅图片,sample对应的图像比较大,target对应的图像比较小,然后将两幅图像都转换成HSV空间,计算sample图像的直方图roiHist,接着调研归一化函数normalize()并对其做归一化处理,归一化到0~255区间,然后输入target图像的hsv空间的target_hsv图像和sample图像归一化了的直方图roiHist,反向计算出图像dst并显示出来。从显示结果可以看出,反向投影的效果能够反映图像的轮廓,可以找到target图像在sample图像中的大致位置。
直方图反向投影示例,运行效果如下:
可以看出,图像直方图的反向投影用于在比较大的输入图像中查找特定图像(通常较小的模板图像最匹配的区域),也就是定位模板图像出现在输入图像的位置。
代码:
"""
图像反向投影1)首先读入两幅图片,sample对应的图像比较大,target对应的图像比较小,2)然后将两幅图像都转换成HSV空间,计算sample图像的直方图roiHist,3)接着调研归一化函数normalize()并对其做归一化处理,归一化到0~255区间,4)然后输入target图像的hsv空间的target_hsv图像和sample图像归一化了的直方图roiHist,反向计算出图像dst并显示出来
"""import numpy as np
import cv2
from matplotlib import pyplot as pltdef main():sample = cv2.imread("Images/BackProjectHist01.png")target = cv2.imread("Images/BackProjectHist02.png")# 图像转HSV空间roi_hsv = cv2.cvtColor(sample, cv2.COLOR_BGR2HSV)target_hsv = cv2.cvtColor(target, cv2.COLOR_BGR2HSV)# 设置窗口属性,并显示图片cv2.namedWindow("sample", cv2.WINDOW_KEEPRATIO)cv2.imshow("sample", sample)# 设置窗口属性,并显示图片cv2.namedWindow("target", cv2.WINDOW_KEEPRATIO)cv2.imshow("target", target)# 计算直方图roiHist = cv2.calcHist([roi_hsv], [0, 1], None, [32, 30], [0, 180, 0, 256])# 直方图归一化cv2.normalize(roiHist, roiHist, 0, 255, cv2.NORM_MINMAX)# 直方图反向投影计算dst = cv2.calcBackProject([target_hsv], [0, 1], roiHist, [0, 180, 0, 256], 1)# 设置窗口属性,并显示图片cv2.namedWindow("Back Projection Demo", cv2.WINDOW_KEEPRATIO)cv2.imshow("Back Projection Demo", dst)cv2.waitKey(0)if __name__ == '__main__':main()