一、实验基础
图像显著性检测
图像的显著性是指对于给定一副真实世界中的场景图像,人们会自动地识别出感兴趣区域,忽略掉不感兴趣的区域,即将注意力集中在图像中某些显著的部分区域。
图像的注意预测,也称视觉显著性检测,指通过智能算法模拟人的视觉系统特点,预测人类的视觉凝视点(就是全神贯注的看一件物品的一个点)和眼动(进行视野搜索的要素),提取图像中的显著性区域(即人类感兴趣的区域)。
Lab颜色空间
Lab颜色空间(Lab color space)是颜色-对立空间,维度L表示亮度,a和b表示颜色对立维度,其常用来描述人眼可见的所有颜色的最完备的色彩空间,它更接近人类对真实场景中色彩的“感觉”。
Lab中的明度通道(L)专门负责整张图的明暗度,简单的说就是整幅图的黑白版。a通道和b通道只负责颜色的多少,而且a通道和b通道的颜色没有亮度。
基于直方图对比度的显著性检测
基于直方图对比度(Histogram-based Contrast, HC)的显著性检测是清华大学程明明等人2011年提出的一种基一种基于颜色全局直方图的颜色对比度算法,像素点与其它像素之间的颜色特征差异度越大,则显著性越高。
- 给定图像D,将其从RGB空间转换到Lab空间,每个像索点的特征为I = [L,a,b]
- 计算像素点(x,y)的显著性值
- 显著值计算公式
- 由于该算法中相同的颜色具有相同的显著值,所以可以采用颜色统计直方图对其进行归类后再运算。
- 颜色归类
- 显著性值计算
图像量化(直方图优化加速)步骤:
1、量化颜色通道。找出图像中一共有多少种颜色以及对应的像素总数。
2、按照像素总数从大到小排序,并同时记录相应颜色。
3、找出像素数目覆盖图像不小于95%的高频颜色,以及其他的不高于5%的颜色种类,假设高频颜色共有maxNum种。
4、把低频颜色的像素归类到与它lab颜色距离相距最近的高频颜色中。
频率调谐(Frequency-tuned, FT)算法
FT算法从频率角度分析图像。图像在频率域可以分成低频部分和高频部分。低频部分反映了图像的整体信息,如物体的轮廓,基本的组成区域。高频部分反映了图像的细节信息,如物体的纹理。显著性区域检测用到的更多的是低频部分的信息。在实际进行计算时,FT方法一般使用窗口5*5的高斯平滑来实现对最高频的舍去。
算法步骤:
- 输入图像进行高斯滤波(一般使用5*5的模板),去除高频信息
- 将原图与滤波后的图像分别从RGB颜色空间转换到Lab颜色空间
- 在原图上计算Lab空间颜色向量平均值 Iu = [Lu, au, bu]
- 计算三个通道均值与高斯滤波后图像的欧氏距离之和,公式如下图所示
算法结构图:
二、实验要求
对提供的 im1、im2、im3 三张图按照HC、FT两种算法进行图像的显著度检测。
三、实验源码与结果
导入包
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 距离计算模块,
from scipy.spatial.distance import pdist, squareform
HC算法显著性检测
'''直方图优化加速'''
def Quantize(img3f,ratio=0.95,colorNums=(12,12,12)):
'''量化三通道图像img3f的颜色,三通道的量化阶数为colorNums
ratio指定最后保留的颜色总数的一定比例数量的颜色
'''
'''量化颜色通道。找出图像中一共有多少种颜色以及对应的像素总数'''
clrTmp = clrTmp = [colorNum-0.0001 for colorNum in colorNums] # clrTmp = [12-0.0001, 12-0.0001, 12-0.0001]
# 指定每个通道分量最多存储多少种的像素
w = [colorNums[1] * colorNums[2], colorNums[2], 1] # 如 w = [12*12,12,1],最后得到的像素种类数量为 w[0]*[w1]*w[2]
# 拆分图像的三个通道的分量(图)
img3f_0,img3f_1,img3f_2 = cv2.split(img3f)
# 各个通道的分量图的像素值(范围:0.0~1.0) * 量化阶数-->强制转为int32类型-->乘以w[i]
# “强制转为int32类型”是量化颜色的关键,这操作大大减少了颜色数量(量化后每个不同的值代表不同的颜色)
idx_img3f_0 = (img3f_0 * clrTmp[0] ).astype(np.int32)* w[0]
idx_img3f_1 = (img3f_1 * clrTmp[1] ).astype(np.int32)* w[1]
idx_img3f_2 = (img3f_2 * clrTmp[2] ).astype(np.int32)* w[2]
# 处理后三个通道分量相加,得到idx1i为颜色量化后的“像素”矩阵,每个不同的"像素值"表示不同的颜色,默认最多为12*12*12种
idx1i = idx_img3f_0 + idx_img3f_1 + idx_img3f_2
# 统计像素出现频数
# np.bincount(arr) 函数:统计 区间[0,arr数组中最大数字]内所有数值 出现的个数(即每种颜色的总数),返回array数组结果
# b = [1,3,2,4,3],a = np.bincount(b),得到a:[0 1 1 2 1]
bincount_pallet = np.bincount(idx1i.reshape(1,-1)[0]) # idx1i.reshape(1,-1)[0]把 idx1i展开成一维的数组
'''按照像素总数从大到小排序,并同时记录相应颜色(这里先从小到大排序)'''
# 按照像素总数从小到大排序,并同时记录相应颜色
sort_pallet = np.sort(bincount_pallet) # 从大到小的像素总数
argsort_pallet = np.argsort(bincount_pallet) # sort_pallet对应的颜色(原索引)
# 合并两个数组:颜色数量,颜色值
numpy_pallet = np.vstack((sort_pallet, argsort_pallet))
# 筛选掉总数为0的颜色
# print('numpy_pallet.shape(before selected): ',numpy_pallet.shape)
numpy_pallet = numpy_pallet[:, np.nonzero(sort_pallet)] # np.nonzero(a)函数返回的是目标数组a中非零元素的索引
# print('numpy_pallet.shape(after selected): ',numpy_pallet.shape)
# 获取筛选后的结果,若总数大于0的颜色值共有351种,则得到的num的形状为(2, 351)
# num[0],num[1]分别存放所有总数不为0的颜色(数值)出现的总数(从小到大排序)以及对应的颜色值(数值)
num = np.swapaxes(numpy_pallet, 0, 1)[0] # np.swapaxes实现数组的维度交换,比如(2, 1, 351) 交换维度后变为(1, 2, 351)
# maxNum 分别为高频颜色种类数
len_num = maxNum = len(num[0]) # len(num[0])为筛选总数为0的颜色后的颜色种类数
'''找出像素数目覆盖图像不小于95%的高频颜色,以及其他的不高于5%的颜色种类,假设高频颜色共有maxNum种'''
height,width = img3f.shape[:2]
maxDropNum = int(np.round(height * width * (1 - ratio))) # 设置删除最大元素阈值
sum_pallet = np.add.accumulate(num[0]) # np.add.accumulate()实现累加,每一个位置的元素和前面的所有元素加起来求和,结果返回numpy数组
# 这里调用 np.argwhere( ) 获取 sum_pallet中元素值>=maxDropNum的元素的索引
arg_sum_pallett = np.argwhere(sum_pallet >= maxDropNum)[0][0] # 前95%的颜色值数量
print('像素数目覆盖图像不小于95%的颜色值数量: ',arg_sum_pallett)
minNum = maxNum - arg_sum_pallett # 后5%的颜色值数量
num_values = num[1][::-1] # 所有高频次颜色值的索引(频次从高到低的颜色值的索引)
minNum = 256 if minNum > 256 else minNum
if minNum <= 10:
minNum = 10 if len(num) > 10 else len(num)
'''把低频颜色的像素归类到与它lab颜色距离相距最近的高频颜色中'''
# 获取由高频颜色组成的三通道图color3i
color3i_init0 = (num_values / w[0]).astype(np.int32)
color3i_init1 = (num_values % w[0]/w[1]).astype(np.int32)
color3i_init2 = (num_values % w[1]).astype(np.int32)
color3i = np.array([color3i_init0,color3i_init1,color3i_init2]).T
zero2maxNum = color3i[:minNum] # 5%的颜色值数量部分
maxNum2len_Num = color3i[minNum:] # 95%的颜色值数量部分
temp_matrix = np.zeros((len_num-minNum,minNum),dtype=np.int32)
for i,single in enumerate(maxNum2len_Num): #分别求:95%的颜色值与5%的颜色值的距离
temp_matrix[i] = np.sum(np.square(single-zero2maxNum),axis=1)
arg_min = np.argmin(temp_matrix, axis=1) # np.argmin(a, axis=指定值)求的就是a在指定的第axis维度(轴)上的最小值对应的位置索引
replaceable_colors = num_values[arg_min] # 通过索引获取5%的颜色值中距离95%的颜色值最近的颜色值
pallet = dict(zip(num_values[:minNum], range(minNum)))
for num_value,index_dist in zip(num_values[minNum:],replaceable_colors):
pallet[num_value] = pallet[index_dist]
idx1i_reshape = idx1i.copy().reshape(1,-1)[0]
idx1i_0 = np.zeros(height * width, dtype=np.int32)
for i, v in enumerate(idx1i_reshape):
idx1i_0[i] = pallet[v]
idx1i = idx1i_0.reshape((height,width))
color3f = np.zeros((1, minNum, 3), np.float32)
colorNum = np.zeros((1, minNum), np.int32)
np.add.at(color3f[0], idx1i, img3f) # 在color3f[0]指定的位置idx1i上 加上指定的值img3f
np.add.at(colorNum[0], idx1i, 1) #在colorNum[0]指定的位置idx1i上 加上指定的值1
colorNum_reshape = colorNum.reshape(color3f.shape[1],1)
color3f[0] /= colorNum_reshape
# 第一个返回结果为量化后的直方图的binN(即直方图的“条形”的数量,比常规的256小了许多)
print('量化后直方图的“条形”数量:',color3f.shape[1])
return color3f.shape[1],idx1i,color3f,colorNum
def GetHC(img_float,delta=0.25):
binN, idx1i, binColor3f, colorNums1i = Quantize(img_float) # 颜色量化
binColor3f = cv2.cvtColor(binColor3f, cv2.COLOR_BGR2Lab) # 颜色空间:BGR2Lab
weight1f = np.zeros(colorNums1i.shape, np.float32)
cv2.normalize(colorNums1i.astype(np.float32), weight1f, 1, 0, cv2.NORM_L1) # 相邻色彩相关权重
binColor3f_reshape = binColor3f.reshape(-1, 3)[:binN]
similar_dist = squareform(pdist(binColor3f_reshape))
similar_dist_sort = np.sort(similar_dist)
similar_dist_argsort = np.argsort(similar_dist)
weight1f = np.tile(weight1f, (binN, 1))
color_weight_dist = np.sum(np.multiply(weight1f, similar_dist), axis=1) # 颜色距离的权重分配
colorSal = np.zeros((1, binN), np.float64)
if colorSal.shape[1] < 2:
return
tmpNum = int(np.round(binN * delta)) # tmpNum 占比0.25的变化的颜色值数量
n = tmpNum if tmpNum > 2 else 2
similar_nVal = similar_dist_sort[:, :n]
totalDist_similar = np.sum(similar_nVal, axis=1)
every_Dist = np.tile(totalDist_similar[:, np.newaxis], (1, n)) - similar_nVal
idx = similar_dist_argsort[:, :n]
val_n = np.take(color_weight_dist,idx) # 获取占比前0.25的颜色权重距离,np.take()沿轴从数组中获取元素
valCrnt = np.sum(val_n[:, :n] * every_Dist, axis=1)
newSal_img = valCrnt / (totalDist_similar * n)
cv2.normalize(newSal_img, newSal_img, 0, 1, cv2.NORM_MINMAX) # 归一化
salHC_img = np.take(newSal_img,idx1i)
cv2.GaussianBlur(salHC_img, (3, 3), 0, salHC_img)
cv2.normalize(salHC_img, salHC_img, 0, 1, cv2.NORM_MINMAX)
return salHC_img
# plt显示多个图像
def plt_shows(titles_l, imgs_l):
for t,im in zip(titles_l,imgs_l):
plt.figure()
plt.title(t)
plt.imshow(im,cmap='gray')
plt.show()
def get_img3_float(img_file):
img3_int = cv2.imread(img_file) # BGR三通道图
img3_float = img3_int.astype(np.float32) # 数组img3_int的副本,强制转换为指定的类型(float32)
img3_float = img3_float / 255.0 # 0~255.0的像素值 映射 到0.0~1.0区间范围
return img3_float
im1_3f = get_img3_float('images_dir/im1.jpg')
im2_3f = get_img3_float('images_dir/im2.jpg')
im3_3f = get_img3_float('images_dir/im3.jpg')
sal1 = GetHC(im1_3f)
sal1 = (sal1*255).astype(np.int32) # 0.0~1.0区间范围的像素值 映射 到 0~255的范围并转换类型
plt_shows(['im1','HC im1'],[im1_3f,sal1])
sal2 = GetHC(im2_3f)
sal2 = (sal2*255).astype(np.int32) # 0.0~1.0区间范围的像素值 映射 到 0~255的范围并转换类型
plt_shows(['im2','HC im2'],[im2_3f,sal2])
sal3 = GetHC(im3_3f)
sal3 = (sal3*255).astype(np.int32) # 0.0~1.0区间范围的像素值 映射 到 0~255的范围并转换类型
plt_shows(['im3','HC im3'],[im3_3f,sal3])
FC算法显著性检测
def ft_detect(img):
'''FC算法显著性检测'''
# 输入图像进行高斯滤波,去除高频信息
img_gauss = cv2.GaussianBlur(img,(5,5), 0.5)
# 将原图与滤波后的图像从BGR颜色空间转换到RGB再转到Lab颜色空间
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_gauss = cv2.cvtColor(img_gauss, cv2.COLOR_BGR2RGB)
img_lab = cv2.cvtColor(img, cv2.COLOR_RGB2LAB)
gauss_lab = cv2.cvtColor(img_gauss, cv2.COLOR_RGB2LAB)
# 在原图上计算Lab空间颜色向量平均值
l_mean = np.mean(img_lab[:,:,0])
a_mean = np.mean(img_lab[:,:,1])
b_mean = np.mean(img_lab[:,:,2])
# 计算三个通道均值与高斯滤波后图像的欧氏距离之和
S_xy = np.square(np.array([l_mean, a_mean, b_mean]) - gauss_lab)
S_xy = np.sum(S_xy,axis=2)
S_xy = S_xy/np.max(S_xy)
return S_xy
# 读取图片(cv2默认读取的是BGR三通道)
img = cv2.imread("images_dir/im1.jpg")
fc_result = ft_detect(img)
plt_shows(['im1','FC im1'],[img,fc_result])
img = cv2.imread("images_dir/im2.jpg")
fc_result = ft_detect(img)
plt_shows(['im2','FC im2'],[img,fc_result])
img = cv2.imread("images_dir/im3.jpg")
fc_result = ft_detect(img)
plt_shows(['im3','FC im3'],[img,fc_result])
四、实验小结
HC方法只用到了颜色特征, 一个像素的显著值是通过与图像中的所有其它像素的色差来定义的。由于测量没考虑空间关系,同样颜色值的像素具有相同的显著值;缩减颜色空间。三通道图像可能的像素值为255*255*255个。这就导致无法使用直方图来进行加速程序,因此我们将每个通道量化为12个颜色,这样就是12*12*12=1278个可能的像素值。
此外,HC算法还从图像中筛选出了可以覆盖图像95%像素的颜色值,剩余的5%的颜色值用直方图中的临近像素替代,从而进一步减少可能的颜色值。为了达到更好的效果,直方图优化后,需要进行一步平滑操作。
FC相对HC算法较简单,它是基于Lab颜色空间像素向量与平均像素向量欧氏距离的显著性检测算法,偏向于利用图像像素的“空间”。
五、参考博文
计算机视觉——图像视觉显著性检测_图像显著性检测_@李忆
OpenCV—python 图像显著性检测算法—HC/RC/LC/FT
【手撕算法】FT显著性检测算法
上一篇:Python计算机视觉基础实验2-边缘检测和角点检测