【1】引言
前述学习进程中对图像处理有了较多实践,但本质上还处于各类函数的初步学习阶段。
真正的应用往往需要面对复杂的图像,因此必须继续学习,加深对各类函数的实践应用。
对运动物体进行识别是一个重重要的方向,虽然已经有一些成熟的行人识别算法,但自己从头写一个对理解算法更有裨益。
今天就以最大类间方差法为学习目标,对方法的原理进行代码复现。
【2】最大类间方差法
【2.1】算法逻辑
最大类间差分法适合用来分割图像,分割后的图像包括前景(多出来的部分,可理解现实环境中为可以移动的图像部分)和背景(可理解为现实环境中不会移动的图像部分),比如辨识一张图像里是否有运动物体时,运动物体就是前景,道路和周围环境就是背景。最大类间差分法是一种非常简单粗暴的算法,辨识一张图像里是否有运动物体的算法模型是:
- 先在无运动的时候抓拍足够多张照片,照片均转化为灰度图,取多张灰度图的像素均值,由像素均值构成一个二维矩阵,这个二维矩阵代表背景图;
- 任取运动时刻的一张照片,转化为灰度图后和背景图作对比,如果像素出现了明显变化,表明出现了运动物体。
- 或者取多张待测照片,照片均转化为灰度图,取多张灰度图的像素均值代表待测图像,将待测图像和背景图作对比,如果像素出现了明显变化,表明出现了运动物体。
图1 最大类间方差法算法模型
【2.2】算法逻辑
最大类间方差法的算法逻辑是针对每一张图像做像素处理的具体过程,具体为:
- 对每一个像素点上的像素值进行抓取;
- 对像素点进行同类项合并,获得不同的像素点类别和各个类别具体的数量;
- 设定阈值,获取像素小于阈值的所有像素点类别占整体类别的比例,可认为是下比例,以及不小于阈值的像素点类别比例,可认为是上比例;
- 计算各个像素值和对应比例的乘积,以阈值为界,取像素小于阈值的所有乘积为下灰度均值,像素不小于阈值的所有乘积为上灰度均值;
- 取下比例和下灰度均值的乘积叠加上比例和上灰度均值的乘积为图像灰度;
- 取上下比例、上下灰度均值差的平方三者相乘为图像类间方差;
- 更换阈值,获得新的类间方差,如果新的类间方差大于前一个类间方差,取当前阈值为最佳阈值;
- 重复3-7,直至获得最大类间方差。
图2 最大类间方差法算法步骤
【2.3】算法应用
最大类间方差法的算法应用的目标是分割图像。分割图像要求先对图像做类间方差运算后,获得最佳阈值后,使用这个阈值对图像做二值处理,二值处理后的图像就是分割好的图像。
【3】算法实现
【3.1】公式梳理
具体的,要想使用最大类间方差法完成图像分割,需要先获取图像的基本属性即像素点数。如果图像的数值像素为M,水平像素为N,则像素点是T为:
(1)
取一个阈值Y,如果有X个像素点小于Y,则有下比例为:
(2)
同时可获得上比例为:
(3)
有时候也可以进一步细化,将下比例转化为每一个像素取值
的比例
之和,具体为:
(4)
对应的下灰度均值计算公式为:
(5)
对应的上灰度均值计算公式为:
(6)
类间方差g为:
(7)
【3.2】代码实现
首先引入必要模块:
import cv2 #引入cv2模块
import numpy as np #引入numpy模块做计算
#from tensorflow.python.ops.gen_logging_ops import image_summary_eager_fallback
import matplotlib.pyplot as plt #引入matplotlib.pyplot模块画图
然后需要对图像的基本属性进行抓取,获得总像素点和每个像素点的比例:
unique_pixels,pixel_count=np.unique(src.flatten(),return_counts=True)# 获取图像的所有像素数total_pixels=src.size# 计算每个像素的比例pixel_ratio=pixel_count/total_pixels
之后要在循环中获得最大类间方差:
# 通过循环体获得最大类间方差gfor threshold_pixel in range(256):# np.where()函数获得元组# 条件判断后,比阈值像素更小的像素点所在的位置索引被提取出来# unique_pixels是一个数组,条件判断时会逐个和阈值作比较# 代码最后的[0]是一个必不可少的取值操作,因为判断后获得的是一个一维数组# 元组的组成形式(array[],),array[]代表一维数组,后面的逗号表明这是一个元组less_than_indices=np.where(unique_pixels<threshold_pixel)[0]# 与小于阈值相对应,还可以取不小于阈值的部分great_than_indices = np.where(unique_pixels >= threshold_pixel)[0]# omega0是小于阈值的所有像素点的比例omega0=np.sum(pixel_ratio[less_than_indices])# omage1和omega0的总和=1omega1=1-omega0# 计算μ0mu0=0for index in less_than_indices:# 以位置索引为依据# 将小于当前阈值的像素值和这个像素值所占的比例直接相乘mu0+=unique_pixels[index]*pixel_ratio[index]# 计算μ1mu1 = 0for index in great_than_indices:# 以位置索引为依据# 将不小于当前阈值的像素值和这个像素值所占的比例直接相乘mu1 += unique_pixels[index] * pixel_ratio[index]# 计算μmu = 0mu = omega0*mu0+omega1*mu1# 计算gg = 0g = omega0 * omega1 * (mu0-mu1) * (mu0-mu1)# 更新最佳取值if g > max_g:max_g=goptimal_threshold=threshold_pixeloptimal_omega0 = omega0optimal_omega1 = omega1optimal_mu0 = mu0optimal_mu1 = mu1optimal_mu = mu
为实现这个过程,需要引入初始图像,将其放在主函数中:
if __name__ == "__main__":image_path = 'src.png' # 请替换为你的图像文件路径analyse_gray_image(image_path)
【3.3】实测效果
初始图像为:
图3 初始图像
转化后的灰度图像为:
图4 灰度图像
最大类间方差法计算结果为:
最大的g值: 1339.25,对应的最优阈值: 58
最大g值对应的 omega0: 0.24, omega1: 0.76, μ0: 8.27, μ1: 93.70, mu: 73.01
可见最优的阈值时58,此时可以取到最大的类间方差。
为了探究其余参数随阈值的变化,因此多提取了一些数据,获得相关图像:
图5 各个参数同类别的关系图
图5中:
- 各个像素的数量实际上随着像素点的分布不会随阈值的变化而变化,是图像的基本属性;
- 类间方差g随阈值的变化在阈值取58时取得极大值;
- 图像平均灰度mu的变化规律和g不同,阈值取58时取得的是一个中间值。
此时的完整代码为:
import cv2
import numpy as np
#from tensorflow.python.ops.gen_logging_ops import image_summary_eager_fallback
import matplotlib.pyplot as pltdef analyse_gray_image(image_path):# 引入图像,直接转化为灰度图src=cv2.imread(image_path,0)cv2.imshow('src',src)cv2.imwrite('srcc.png',src)if src is None:print('无法读取图片')return# 对像素进行统计,列出每个像素的个数# src的像素是二维数组,像素所在的(横向像素位置X纵向像素位置)组成二维数组# flatten()函数是将src的像素数转化为一维数组# np.unique()会进行同类项合并工作,列出类别和每个类内部的数量unique_pixels,pixel_count=np.unique(src.flatten(),return_counts=True)# 获取图像的所有像素数total_pixels=src.size# 计算每个像素的比例pixel_ratio=pixel_count/total_pixels# 初始化最大的gmax_g=0# 初始化最优值optimal_threshold=0optimal_omega0=0optimal_omega1=0optimal_mu0=0optimal_mu1=0optimal_mu=0optimal_below_threshold_indices = []optimal_above_threshold_indices = []optimal_below_threshold_pixels = []optimal_above_threshold_pixels = []optimal_below_threshold_pixel_counts = []optimal_above_threshold_pixel_counts = []optimal_unique_pixels = []optimal_pixel_count = []optimal_omega0 = []omega0_v = []optimal_omega1 = []omega1_v = []optimal_muv = []mu_v = []optimal_g=[]g_v = []for threshold_pixel in unique_pixels:# np.where()函数获得元组# 条件判断后,比阈值像素更小的像素点所在的位置索引被提取出来# unique_pixels是一个数组,条件判断时会逐个和阈值作比较# 代码最后的[0]是一个必不可少的取值操作,因为判断后获得的是一个一维数组# 元组的组成形式(array[],),array[]代表一维数组,后面的逗号表明这是一个元组less_than_indices=np.where(unique_pixels<threshold_pixel)[0]# 与小于阈值相对应,还可以取不小于阈值的部分great_than_indices = np.where(unique_pixels >= threshold_pixel)[0]# omega0是小于阈值的所有像素点的比例omega0=np.sum(pixel_ratio[less_than_indices])# omage1和omega0的总和=1omega1=1-omega0# 计算μ0mu0=0for index in less_than_indices:# 以位置索引为依据# 将小于当前阈值的像素值和这个像素值所占的比例直接相乘mu0+=unique_pixels[index]*pixel_ratio[index]# 计算μ1mu1 = 0for index in great_than_indices:# 以位置索引为依据# 将不小于当前阈值的像素值和这个像素值所占的比例直接相乘mu1 += unique_pixels[index] * pixel_ratio[index]# 计算μmu = 0mu = omega0*mu0+omega1*mu1mu_v.append(mu)# 计算gg = 0g = omega0 * omega1 * (mu0-mu1) * (mu0-mu1)g_v.append(g)# 更新最佳取值if g > max_g:max_g=goptimal_threshold=threshold_pixeloptimal_omega0 = omega0optimal_omega1 = omega1optimal_mu0 = mu0optimal_mu1 = mu1optimal_mu = muoptimal_below_threshold_indices = less_than_indicesoptimal_above_threshold_indices = great_than_indicesoptimal_below_threshold_pixels = unique_pixels[optimal_below_threshold_indices]optimal_above_threshold_pixels = unique_pixels[optimal_above_threshold_indices]optimal_below_threshold_pixel_counts = pixel_count[optimal_below_threshold_indices]optimal_above_threshold_pixel_counts = pixel_count[optimal_above_threshold_indices]optimal_unique_pixels = unique_pixelsoptimal_pixel_count = pixel_countoptimal_g = g_voptimal_muv = mu_vprint(f"最大的g值: {max_g:.2f},对应的最优阈值: {optimal_threshold}")print(f"最大g值对应的 omega0: {optimal_omega0:.2f}, omega1: {optimal_omega1:.2f}, μ0: {optimal_mu0:.2f}, μ1: {optimal_mu1:.2f}, mu: {optimal_mu:.2f}")# 根据最优阈值划分像素# 获得小于阈值像素点的像素对应的位置索引#below_threshold_indices = np.where(unique_pixels < optimal_threshold)[0]# 获得不小于阈值像素点的像素对应的位置索引#above_threshold_indices = np.where(unique_pixels >= optimal_threshold)[0]# 根据位置索引获得小于阈值像素点的所有像素#below_threshold_pixels = unique_pixels[below_threshold_indices]# 根据位置索引获得不小于阈值像素点的所有像素#above_threshold_pixels = unique_pixels[above_threshold_indices]# 根据位置索引获得小于阈值像素点的各类像素的数量#below_threshold_pixels_counts = pixel_count[below_threshold_indices]# 根据位置索引获得不小于阈值像素点的各类像素的数量#above_threshold_pixels_counts = pixel_count[above_threshold_indices]fig,ax = plt.subplots(3,1,sharex=True)ax[0].bar(optimal_unique_pixels,optimal_pixel_count,width=1)#ax[0].set_xlabel('unique_pixels')ax[0].set_ylabel('pixel_count')ax[0].set_title('unique_pixels vs count')ax[0].grid(True)ax[1].bar(optimal_unique_pixels,optimal_g,width=1)#ax[1].set_xlabel('unique_pixels')ax[1].set_ylabel('g')ax[1].set_title('unique_pixels vs g')ax[1].grid(True)ax[2].bar(optimal_unique_pixels,optimal_muv,width=1)ax[2].set_xlabel('unique_pixels')ax[2].set_ylabel('mu')ax[2].set_title('unique_pixels vs mu')ax[2].grid(True)# 调整子图之间的间距plt.tight_layout()plt.savefig('srccc.png')plt.show()if __name__ == "__main__":image_path = 'src1.png' # 请替换为你的图像文件路径analyse_gray_image(image_path)
【4】细节说明
图5中的unique_pixels既代表实际的像素取值,也代表计算过程取的阈值,因为阈值遍历每个像素值。
代码中还提供了其他的一些参数在最佳阈值时的取值:
# 更新最佳取值if g > max_g:max_g=goptimal_threshold=threshold_pixeloptimal_omega0 = omega0optimal_omega1 = omega1optimal_mu0 = mu0optimal_mu1 = mu1optimal_mu = muoptimal_below_threshold_indices = less_than_indicesoptimal_above_threshold_indices = great_than_indicesoptimal_below_threshold_pixels = unique_pixels[optimal_below_threshold_indices]optimal_above_threshold_pixels = unique_pixels[optimal_above_threshold_indices]optimal_below_threshold_pixel_counts = pixel_count[optimal_below_threshold_indices]optimal_above_threshold_pixel_counts = pixel_count[optimal_above_threshold_indices]optimal_unique_pixels = unique_pixelsoptimal_pixel_count = pixel_countoptimal_g = g_voptimal_muv = mu_v
以及获得此时的像素点分类至少有两种方法,第一种方法在上述的if代码中获得,另一种重新计算了一次,可以删除注释标识符“#”获取:
# 根据最优阈值划分像素# 获得小于阈值像素点的像素对应的位置索引#below_threshold_indices = np.where(unique_pixels < optimal_threshold)[0]# 获得不小于阈值像素点的像素对应的位置索引#above_threshold_indices = np.where(unique_pixels >= optimal_threshold)[0]# 根据位置索引获得小于阈值像素点的所有像素#below_threshold_pixels = unique_pixels[below_threshold_indices]# 根据位置索引获得不小于阈值像素点的所有像素#above_threshold_pixels = unique_pixels[above_threshold_indices]# 根据位置索引获得小于阈值像素点的各类像素的数量#below_threshold_pixels_counts = pixel_count[below_threshold_indices]# 根据位置索引获得不小于阈值像素点的各类像素的数量#above_threshold_pixels_counts = pixel_count[above_threshold_indices]
【5】总结
学习了最大类间方差法的基本知识。