前言
文本主要解析在传统机器学习当中一些小的算法与思想,只是传统机器学习算法当中的一小部分,更多传统机器学习算法可参考我的另外几篇博客
链接1: PCA主成分分析
链接2: Canny边缘检测算法
链接3: K-Means聚类算法
链接4: SIFT算法分析
1. opencv
- OpenCV是一个开源的计算机视觉库,可以从 http://opencv.org 获取。
- OpenCV 库用C语言和 C++ 语言编写,可以在 Windows、Linux、Mac OS X 等系统运行。同时也在积极开发 Python、Java、Matlab 以及其他一些语言的接口,将库导入安卓和 iOS 中为移动设备开发应用。
- OpenCV 设计用于进行高效的计算,十分强调实时应用的开发。它由 C++ 语言编写并进行了深度优化,从而可以享受多线程处理的优势。
- OpenCV 的一个目标是提供易于使用的计算机视觉接口,从而帮助人们快速建立精巧的视觉应用。
- OpenCV 库包含从计算机视觉各个领域衍生出来的 500 多个函数,包括工业产品质量检验、医学图像处理、安保领域、交互操作、相机校正、双目视觉以及机器人学
因为opencv的底层实现是由C++写的,C++的最大优势在于高效,相同类型的·函数在不同的库当中运行速度是不一样的。
opencv大坑之BGR
opencv对于读进来的图片的通道排列是BGR,而不是主流的RGB!谨记!
#opencv读入的矩阵是BGR,如果想转为RGB,可以这么转
img4 = cv2.imread(‘1.jpg’)
img4 = cv2.cvtColor(img4,cv2.COLOR_BGR2RGB)
注意点
- 除了opencv读入的彩色图片以BGR顺序存储外,其他所有图像库读入彩色图片都以RGB存储。
- 除了PIL读入的图片是img类之外,其他库读进来的图片都是以numpy 矩阵。
- 各大图像库的性能,最好的OpenCv,无论是速度还是图片操作的全面性,都属于碾压的存在, 毕竟他是一个巨大的cv专用库。
下面是一组实验数据,一张3120*4160的图像,跑100次所花费的时间
2. 线性回归
什么是线性回归?
举个例子,某商品的利润在售价为2元、5元、10元时分别为4元、10元、20元, 我们很容易得出商品的利润与售价的关系符合直线:y=2x. 在上面这个简单的一元线性回归方程中,我们称“2”为回归系数,即斜率为其回归系数。 回归系数表示商品的售价(x)每变动一个单位,其利润(y)与之对应的变动关系。
线性回归表示这些离散的点总体上“最逼近”哪条直线。类似于均值的概念。
最小二乘法
- 关键点在于最小化误差的平方和
- 假设我们现在有一系列的数据点(xi,yi) (i=1,…,m),那么由我们给出的拟合函数h(x)得到的估计量就是h(xi)
- 残差:ri = h(xi) – yi
- 我们要追求的目标就是使得残差平方和最小
由此,我们可以写出最小二乘法的定义了:
这是一个无约束的最优化问题,分别对k和b求偏导,然后令偏导数为0,即可获得极值点。
3. RANSAC
- 随机采样一致性
- 注意,RANSAC是一种思想,一个求解已知模型的参数的框架。它不限定某一特定的问题,可以是计 算机视觉的问题,同样也可以是统计数学,甚至可以是经济学领域的模型参数估计问题。
- 它是一种迭代的方法,用来在一组包含离群的被观测数据中估算出数学模型的参数。 RANSAC是一个非确定性算法,在某种意义上说,它会产生一个在一定概率下合理的结果,其允许使用更多次的迭代来使其概率增加。
- RANSAC的基本假设是 “内群”数据可以通过几组模型参数来叙述其数据分布,而“离群”数据则是不适合模型化的数据。 数据会受噪声影响,噪声指的是离群,例如从极端的噪声或错误解释有关数据的测量或不正确的假设。 RANSAC假定,给定一组(通常很小的)内群,存在一个程序,这个程序可以估算最佳解释或最适用于这一数据模型的参数。
注意,RANSAC只是一种思想,是用来求已知模型参数的框架,对于已知的模型是没有限定的,它可以是任何的模型,但是是个模型就会有参数,比如y = ax + b。a,b分别等于多少才是RANSAC需要求解的问题,也就是说无论你是y = ax + b,还是y = kx + c,亦或是z = ax + by + c。RANSAC是不管的,你只要按着它的方法走,就能把参数求出来。比如说把大象放在冰箱需要3步,那么我们不管能不能放入,而只关心这三步的动作,因为此时的冰箱相当于一个黑盒。所以不管我们是把大象放进冰箱还是把飞机放进冰箱,RANSAC并不关心,RANSAC只关心这三步动作,而这三步动作不会因为物体的不同而不同。
RANSAC与最小二乘法
- 生产实践中的数据往往会有一定的偏差。
- 例如我们知道两个变量X与Y之间呈线性关系,Y=aX+b,我们想确定参数a与b的具体值。通过实验, 可以得到一组X与Y的测试值。虽然理论上两个未知数的方程只需要两组值即可确认,但由于系统误 差的原因,任意取两点算出的a与b的值都不尽相同。我们希望的是,最后计算得出的理论模型与测 试值的误差最小。
- 最小二乘法:通过计算最小均方差关于参数a、b的偏导数为零时的值。事实上,很多情况下,最小 二乘法都是线性回归的代名词。
- 遗憾的是,最小二乘法只适合于误差较小的情况。
- 在模型确定以及最大迭代次数允许的情况下,RANSAC总是能找到最优解。(对于包含80%误差的数 据集,RANSAC的效果远优于直接的最小二乘法。)
- 由于一张图片中像素点数量大,采用最小二乘法运算量大,计算速度慢。
观察上面那幅图,如果使用最小二乘法进行拟合,得到的会是红色的那条线,明显与预期效果有很大偏差。这是因为最小二乘法是对噪声点不敏感的,所以最小二乘法只适用于误差较小的情况,而我们利用RANSAC方法就能得到合理的解
RANSAC的步骤
RANSAC算法的输入:
- 一组观测数据(往往含有较大的噪声或无效点)
- 一个用于解释观测数据的参数化模型,比如 y=ax+b(即模型是已知的)
- 一些可信的参数
- 在数据中随机选择几个点设定为内群
- 计算适合内群的模型 e.g. y=ax+b ->y=2x+3 y=4x+5
- 把其它刚才没选到的点带入刚才建立的模型中,计算是否为内群 e.g. hi=2xi+3->ri
- 记下内群数量
- 重复以上步骤
- 比较哪次计算中内群数量最多,内群最多的那次所建的模型就是我们所要求的解
注意:不同问题对应的数学模型不同,因此在计算模型参数时方法必定不同,RANSAC的作用不在于 计算模型参数。(这导致ransac的缺点在于要求数学模型已知)
这里有几个问题:
- 一开始的时候我们要随机选择多少点(n)
- 以及要重复做多少次(k)
RANSAC的参数确定
- 假设每个点是真正内群的概率为 w:
w = 内群的数目/(内群数目+外群数目) - 通常我们不知道 w 是多少, w^n是所选择的n个点都是内群的机率, 1-w^n 是所选择的n个点至少有一个 不是内群的机率, (1 − wn)k 是表示重复 k 次都没有全部的n个点都是内群的机率, 假设算法跑 k 次以后 成功的机率是p,那么:
1 − p = (1 − wn)k
p = 1 − (1 − wn)k - 我们可以通过P反算得到抽取次数K,K=log(1-P)/log(1-w^n)。
- 所以如果希望成功机率高:
- 当n不变时,k越大,则p越大; 当w不变时,n越大,所需的k就越大。
- 通常w未知,所以n 选小一点比较好。
RANSAC的优缺点
优点:
- 它能鲁棒的估计模型参数。例如,它能从包含大量局外点的数据集中估计出高精度的参数。
缺点:
- 它计算参数的迭代次数没有上限;如果设置迭代次数的上限,得到的结果可能不是最优的结果,甚 至可能得到错误的结果。
- RANSAC只有一定的概率得到可信的模型,概率与迭代次数成正比。
- 它要求设置跟问题相关的阀值。
- RANSAC只能从特定的数据集中估计出一个模型,如果存在两个(或多个)模型,RANSAC不能找到 别的模型。
- 要求数学模型已知
代码实现
import numpy as np
import scipy as sp
import scipy.linalg as sl
def ransac(data, model, n, k, t, d, debug = False, return_all = False):
"""
输入:
data - 样本点
model - 假设模型:事先自己确定
n - 生成模型所需的最少样本点
k - 最大迭代次数
t - 阈值:作为判断点满足模型的条件
d - 拟合较好时,需要的样本点最少的个数,当做阈值看待
输出:
bestfit - 最优拟合解(返回nil,如果未找到)
iterations = 0
bestfit = nil #后面更新
besterr = something really large #后期更新besterr = thiserr
while iterations < k
{
maybeinliers = 从样本中随机选取n个,不一定全是局内点,甚至全部为局外点
maybemodel = n个maybeinliers 拟合出来的可能符合要求的模型
alsoinliers = emptyset #满足误差要求的样本点,开始置空
for (每一个不是maybeinliers的样本点)
{
if 满足maybemodel即error < t
将点加入alsoinliers
}
if (alsoinliers样本点数目 > d)
{
%有了较好的模型,测试模型符合度
bettermodel = 利用所有的maybeinliers 和 alsoinliers 重新生成更好的模型
thiserr = 所有的maybeinliers 和 alsoinliers 样本点的误差度量
if thiserr < besterr
{
bestfit = bettermodel
besterr = thiserr
}
}
iterations++
}
return bestfit
"""
iterations = 0
bestfit = None
besterr = np.inf #设置默认值
best_inlier_idxs = None
while iterations < k:
maybe_idxs, test_idxs = random_partition(n, data.shape[0])
print ('test_idxs = ', test_idxs)
maybe_inliers = data[maybe_idxs, :] #获取size(maybe_idxs)行数据(Xi,Yi)
test_points = data[test_idxs] #若干行(Xi,Yi)数据点
maybemodel = model.fit(maybe_inliers) #拟合模型
test_err = model.get_error(test_points, maybemodel) #计算误差:平方和最小
print('test_err = ', test_err <t)
also_idxs = test_idxs[test_err < t]
print ('also_idxs = ', also_idxs)
also_inliers = data[also_idxs,:]
if debug:
print ('test_err.min()',test_err.min())
print ('test_err.max()',test_err.max())
print ('numpy.mean(test_err)',numpy.mean(test_err))
print ('iteration %d:len(alsoinliers) = %d' %(iterations, len(also_inliers)) )
# if len(also_inliers > d):
print('d = ', d)
if (len(also_inliers) > d):
betterdata = np.concatenate( (maybe_inliers, also_inliers) ) #样本连接
bettermodel = model.fit(betterdata)
better_errs = model.get_error(betterdata, bettermodel)
thiserr = np.mean(better_errs) #平均误差作为新的误差
if thiserr < besterr:
bestfit = bettermodel
besterr = thiserr
best_inlier_idxs = np.concatenate( (maybe_idxs, also_idxs) ) #更新局内点,将新点加入
iterations += 1
if bestfit is None:
raise ValueError("did't meet fit acceptance criteria")
if return_all:
return bestfit,{'inliers':best_inlier_idxs}
else:
return bestfit
def random_partition(n, n_data):
"""return n random rows of data and the other len(data) - n rows"""
all_idxs = np.arange(n_data) #获取n_data下标索引
np.random.shuffle(all_idxs) #打乱下标索引
idxs1 = all_idxs[:n]
idxs2 = all_idxs[n:]
return idxs1, idxs2
class LinearLeastSquareModel:
#最小二乘求线性解,用于RANSAC的输入模型
def __init__(self, input_columns, output_columns, debug = False):
self.input_columns = input_columns
self.output_columns = output_columns
self.debug = debug
def fit(self, data):
#np.vstack按垂直方向(行顺序)堆叠数组构成一个新的数组
A = np.vstack( [data[:,i] for i in self.input_columns] ).T #第一列Xi-->行Xi
B = np.vstack( [data[:,i] for i in self.output_columns] ).T #第二列Yi-->行Yi
x, resids, rank, s = sl.lstsq(A, B) #residues:残差和
return x #返回最小平方和向量
def get_error(self, data, model):
A = np.vstack( [data[:,i] for i in self.input_columns] ).T #第一列Xi-->行Xi
B = np.vstack( [data[:,i] for i in self.output_columns] ).T #第二列Yi-->行Yi
B_fit = sp.dot(A, model) #计算的y值,B_fit = model.k*A + model.b
err_per_point = np.sum( (B - B_fit) ** 2, axis = 1 ) #sum squared error per row
return err_per_point
def test():
#生成理想数据
n_samples = 500 #样本个数
n_inputs = 1 #输入变量个数
n_outputs = 1 #输出变量个数
A_exact = 20 * np.random.random((n_samples, n_inputs))#随机生成0-20之间的500个数据:行向量
perfect_fit = 60 * np.random.normal( size = (n_inputs, n_outputs) ) #随机线性度,即随机生成一个斜率
B_exact = sp.dot(A_exact, perfect_fit) # y = x * k
#加入高斯噪声,最小二乘能很好的处理
A_noisy = A_exact + np.random.normal( size = A_exact.shape ) #500 * 1行向量,代表Xi
B_noisy = B_exact + np.random.normal( size = B_exact.shape ) #500 * 1行向量,代表Yi
if 1:
#添加"局外点"
n_outliers = 100
all_idxs = np.arange( A_noisy.shape[0] ) #获取索引0-499
np.random.shuffle(all_idxs) #将all_idxs打乱
outlier_idxs = all_idxs[:n_outliers] #100个0-500的随机局外点
A_noisy[outlier_idxs] = 20 * np.random.random( (n_outliers, n_inputs) ) #加入噪声和局外点的Xi
B_noisy[outlier_idxs] = 50 * np.random.normal( size = (n_outliers, n_outputs)) #加入噪声和局外点的Yi
#setup model
all_data = np.hstack( (A_noisy, B_noisy) ) #形式([Xi,Yi]....) shape:(500,2)500行2列
input_columns = range(n_inputs) #数组的第一列x:0
output_columns = [n_inputs + i for i in range(n_outputs)] #数组最后一列y:1
debug = False
model = LinearLeastSquareModel(input_columns, output_columns, debug = debug) #类的实例化:用最小二乘生成已知模型
linear_fit,resids,rank,s = sp.linalg.lstsq(all_data[:,input_columns], all_data[:,output_columns])
#run RANSAC 算法
ransac_fit, ransac_data = ransac(all_data, model, 50, 1000, 7e3, 300, debug = debug, return_all = True)
if 1:
import pylab
sort_idxs = np.argsort(A_exact[:,0])
A_col0_sorted = A_exact[sort_idxs] #秩为2的数组
if 1:
pylab.plot( A_noisy[:,0], B_noisy[:,0], 'k.', label = 'data' ) #散点图
pylab.plot( A_noisy[ransac_data['inliers'], 0], B_noisy[ransac_data['inliers'], 0], 'bx', label = "RANSAC data" )
else:
pylab.plot( A_noisy[non_outlier_idxs,0], B_noisy[non_outlier_idxs,0], 'k.', label='noisy data' )
pylab.plot( A_noisy[outlier_idxs,0], B_noisy[outlier_idxs,0], 'r.', label='outlier data' )
pylab.plot( A_col0_sorted[:,0],
np.dot(A_col0_sorted,ransac_fit)[:,0],
label='RANSAC fit' )
pylab.plot( A_col0_sorted[:,0],
np.dot(A_col0_sorted,perfect_fit)[:,0],
label='exact system' )
pylab.plot( A_col0_sorted[:,0],
np.dot(A_col0_sorted,linear_fit)[:,0],
label='linear fit' )
pylab.legend()
pylab.show()
if __name__ == "__main__":
test()
结果:
4. 图像相似度比较哈希算法
相似图像搜索的哈希算法有三种:
- 均值哈希算法
- 差值哈希算法
- 感知哈希算法
什么是哈希(Hash)?
- 散列函数(或散列算法,又称哈希函数,英语:Hash Function)是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums, 或hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表。
- 通过哈希算法得到的任意长度的二进制值映射为较短的固定长度的二进制值,即哈希值。此外, 哈希值是一段数据唯一且极其紧凑的数值表示形式,如果通过哈希一段明文得到哈希值,哪怕只 更改该段明文中的任意一个字母,随后得到的哈希值都将不同。
- 哈希算法是一个函数,能够把几乎所有的数字文件都转换成一串由数字和字母构成的看似乱码的 字符串。
哈希函数作为一种加密函数,其拥有两个最重要特点:
- 不可逆性。输入信息得出输出的那个看似乱码的字符串(哈希值)非常容易,但是从输出的字符串反推出输入的结果却是却非常非常难
- 输出值唯一性和不可预测性。只要输入的信息有一点点区别,那么根据哈希算法得出来的输出值也相差甚远。
汉明距离
两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。
均值哈希算法
步骤
- 缩放:图片缩放为8*8,保留结构,除去细节。
- 灰度化:转换为灰度图。
- 求平均值:计算灰度图所有像素的平均值。
- 比较:像素值大于平均值记作1,相反记作0,总共64位。
- 生成hash:将上述步骤生成的1和0按顺序组合起来既是图片的指纹(hash)
- 对比指纹:将两幅图的指纹对比,计算汉明距离,即两个64位的hash值有多少位是不一样的,不相同位数越少,图片越相似。
差值哈希算法
差值哈希算法相较于均值哈希算法,前期和后期基本相同,只有中间比较hash有变化。
步骤
- 缩放:图片缩放为8*9,保留结构,除去细节。
- 灰度化:转换为灰度图。
- 求平均值:计算灰度图所有像素的平均值。 —这步没有,只是为了与均值哈希做对比
- 比较:像素值大于后一个像素值记作1,相反记作0。本行不与下一行对比,每行9个像素, 八个差值,有8行,总共64位
- 生成hash:将上述步骤生成的1和0按顺序组合起来既是图片的指纹(hash)。
- 对比指纹:将两幅图的指纹对比,计算汉明距离,即两个64位的hash值有多少位是不一样 的,不相同位数越少,图片越相似。
感知哈希算法
均值哈希算法过于严格,不够精确,更适合搜索缩略图,为了获得更精确的结果可以选择感知哈希 算法,它采用的是DCT(离散余弦变换)来降低频率的方法。
步骤:
- 缩小图片:32 * 32是一个较好的大小,这样方便DCT计算
- 转化为灰度图:把缩放后的图片转化为灰度图。
- 计算DCT:DCT把图片分离成分率的集合
- 缩小DCT:DCT计算后的矩阵是32 * 32,保留左上角的8 * 8,这些代表图片的最低频率。
- 计算平均值:计算缩小DCT后的所有像素点的平均值。
- 进一步减小DCT:大于平均值记录为1,反之记录为0.
- 得到信息指纹:组合64个信息位,顺序随意保持一致性。
- 最后比对两张图片的指纹,获得汉明距离即可。
代码实现
均值哈希和差值哈希的实现:
import cv2
import numpy as np
#均值哈希算法
def aHash(img):
#缩放为8*8
img=cv2.resize(img,(8,8),interpolation=cv2.INTER_CUBIC)
#转换为灰度图
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#s为像素和初值为0,hash_str为hash值初值为''
s=0
hash_str=''
#遍历累加求像素和
for i in range(8):
for j in range(8):
s=s+gray[i,j]
#求平均灰度
avg=s/64
#灰度大于平均值为1相反为0生成图片的hash值
for i in range(8):
for j in range(8):
if gray[i,j]>avg:
hash_str=hash_str+'1'
else:
hash_str=hash_str+'0'
return hash_str
#差值算法
def dHash(img):
#缩放8*9
img=cv2.resize(img,(9,8),interpolation=cv2.INTER_CUBIC)
#转换灰度图
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
hash_str=''
#每行前一个像素大于后一个像素为1,相反为0,生成哈希
for i in range(8):
for j in range(8):
if gray[i,j]>gray[i,j+1]:
hash_str=hash_str+'1'
else:
hash_str=hash_str+'0'
return hash_str
#Hash值对比
def cmpHash(hash1,hash2):
n=0
#hash长度不同则返回-1代表传参出错
if len(hash1)!=len(hash2):
return -1
#遍历判断
for i in range(len(hash1)):
#不相等则n计数+1,n最终为相似度
if hash1[i]!=hash2[i]:
n=n+1
return n
img1=cv2.imread('lenna.png')
img2=cv2.imread('lenna_noise.png')
hash1= aHash(img1)
hash2= aHash(img2)
print(hash1)
print(hash2)
n=cmpHash(hash1,hash2)
print('均值哈希算法相似度:',n)
hash1= dHash(img1)
hash2= dHash(img2)
print(hash1)
print(hash2)
n=cmpHash(hash1,hash2)
print('差值哈希算法相似度:',n)
三种算法的比较:
- aHash:均值哈希。速度比较快,但是有时不太精确。
- pHash:感知哈希。精确度较高,但是速度方面较差一些
- dHash:差值哈希。精确度较高,且速度也非常快。
素材生成
import cv2 as cv
import numpy as np
from PIL import Image
import os.path as path
from PIL import ImageEnhance
def rotate(image):
def rotate_bound(image, angle):
# grab the dimensions of the image and then determine the
# center
(h, w) = image.shape[:2]
(cX, cY) = (w // 2, h // 2)
# grab the rotation matrix (applying the negative of the
# angle to rotate clockwise), then grab the sine and cosine
# (i.e., the rotation components of the matrix)
M = cv.getRotationMatrix2D((cX, cY), -angle, 1.0)
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])
# compute the new bounding dimensions of the image
nW = int((h * sin) + (w * cos))
nH = int((h * cos) + (w * sin))
# adjust the rotation matrix to take into account translation
M[0, 2] += (nW / 2) - cX
M[1, 2] += (nH / 2) - cY
# perform the actual rotation and return the image
return cv.warpAffine(image, M, (nW, nH))
return rotate_bound(image, 45)
def enhance_color(image):
enh_col = ImageEnhance.Color(image)
color = 1.5
return enh_col.enhance(color)
def blur(image):
# 模糊操作
return cv.blur(image, (15, 1))
def sharp(image):
# 锐化操作
kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32)
return cv.filter2D(image, -1, kernel=kernel)
def contrast(image):
def contrast_brightness_image(src1, a, g):
"""
粗略的调节对比度和亮度
:param src1: 图片
:param a: 对比度
:param g: 亮度
:return:
"""
# 获取shape的数值,height和width、通道
h, w, ch = src1.shape
# 新建全零图片数组src2,将height和width,类型设置为原图片的通道类型(色素全为零,输出为全黑图片)
src2 = np.zeros([h, w, ch], src1.dtype)
# addWeighted函数说明如下
return cv.addWeighted(src1, a, src2, 1 - a, g)
return contrast_brightness_image(image, 1.2, 1)
def resize(image):
# 缩放图片
return cv.resize(image, (0, 0), fx=1.25, fy=1)
def light(image):
# 修改图片的亮度
return np.uint8(np.clip((1.3 * image + 10), 0, 255))
def save_img(image, img_name, output_path=None):
# 保存图片
cv.imwrite(path.join(output_path, img_name), image, [int(cv.IMWRITE_JPEG_QUALITY), 70])
pass
def show_img(image):
cv.imshow('image', image)
cv.waitKey(0)
pass
def main():
data_img_name = 'lenna.png'
output_path = "./source"
data_path = path.join(output_path, data_img_name)
img = cv.imread(data_path)
# 修改图片的亮度
img_light = light(img)
# 修改图片的大小
img_resize = resize(img)
# 修改图片的对比度
img_contrast = contrast(img)
# 锐化
img_sharp = sharp(img)
# 模糊
img_blur = blur(img)
# 色度增强
img_color = enhance_color(Image.open(data_path))
# 旋转
img_rotate = rotate(img)
img_rotate1 = Image.open(data_path).rotate(45)
# 两张图片横向合并(便于对比显示)
# tmp = np.hstack((img, img_rotate))
save_img(img_light, "%s_light.jpg" % data_img_name.split(".")[0], output_path)
save_img(img_resize, "%s_resize.jpg" % data_img_name.split(".")[0], output_path)
save_img(img_contrast, "%s_contrast.jpg" % data_img_name.split(".")[0], output_path)
save_img(img_sharp, "%s_sharp.jpg" % data_img_name.split(".")[0], output_path)
save_img(img_blur, "%s_blur.jpg" % data_img_name.split(".")[0], output_path)
# save_img(img_rotate, "%s_rotate.jpg" % data_img_name.split(".")[0], output_path)
# 色度增强
img_color.save(path.join(output_path, "%s_color.jpg" % data_img_name.split(".")[0]))
img_rotate1.save(path.join(output_path, "%s_rotate.jpg" % data_img_name.split(".")[0]))
show_img(img_rotate)
pass
if __name__ == '__main__':
main()
算法比较代码
import cv2
import numpy as np
import time
import os.path as path
def aHash(img, width=8, high=8):
"""
均值哈希算法
:param img: 图像数据
:param width: 图像缩放的宽度
:param high: 图像缩放的高度
:return:感知哈希序列
"""
# 缩放为8*8
img = cv2.resize(img, (width, high), interpolation=cv2.INTER_CUBIC)
# 转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# s为像素和初值为0,hash_str为hash值初值为''
s = 0
hash_str = ''
# 遍历累加求像素和
for i in range(8):
for j in range(8):
s = s + gray[i, j]
# 求平均灰度
avg = s / 64
# 灰度大于平均值为1相反为0生成图片的hash值
for i in range(8):
for j in range(8):
if gray[i, j] > avg:
hash_str = hash_str + '1'
else:
hash_str = hash_str + '0'
return hash_str
def dHash(img, width=9, high=8):
"""
差值感知算法
:param img:图像数据
:param width:图像缩放后的宽度
:param high: 图像缩放后的高度
:return:感知哈希序列
"""
# 缩放8*8
img = cv2.resize(img, (width, high), interpolation=cv2.INTER_CUBIC)
# 转换灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hash_str = ''
# 每行前一个像素大于后一个像素为1,反之置为0,生成感知哈希序列(string)
for i in range(high):
for j in range(high):
if gray[i, j] > gray[i, j + 1]:
hash_str = hash_str + '1'
else:
hash_str = hash_str + '0'
return hash_str
def cmp_hash(hash1, hash2):
"""
Hash值对比
:param hash1: 感知哈希序列1
:param hash2: 感知哈希序列2
:return: 返回相似度
"""
n = 0
# hash长度不同则返回-1代表传参出错
if len(hash1) != len(hash2):
return -1
# 遍历判断
for i in range(len(hash1)):
# 不相等则n计数+1,n最终为相似度
if hash1[i] != hash2[i]:
n = n + 1
return 1 - n / len(hash2)
def pHash(img_file, width=64, high=64):
"""
感知哈希算法
:param img_file: 图像数据
:param width: 图像缩放后的宽度
:param high:图像缩放后的高度
:return:图像感知哈希序列
"""
# 加载并调整图片为32x32灰度图片
img = cv2.imread(img_file, 0)
img = cv2.resize(img, (width, high), interpolation=cv2.INTER_CUBIC)
# 创建二维列表
h, w = img.shape[:2]
vis0 = np.zeros((h, w), np.float32)
vis0[:h, :w] = img # 填充数据
# 二维Dct变换
vis1 = cv2.dct(cv2.dct(vis0))
vis1.resize(32, 32)
# 把二维list变成一维list
img_list = vis1.flatten()
# 计算均值
avg = sum(img_list) * 1. / len(img_list)
avg_list = ['0' if i > avg else '1' for i in img_list]
# 得到哈希值
return ''.join(['%x' % int(''.join(avg_list[x:x + 4]), 2) for x in range(0, 32 * 32, 4)])
def hamming_dist(s1, s2):
return 1 - sum([ch1 != ch2 for ch1, ch2 in zip(s1, s2)]) * 1. / (32 * 32 / 4)
def concat_info(type_str, score, time):
temp = '%s相似度:%.2f %% -----time=%.4f ms' % (type_str, score * 100, time)
print(temp)
return temp
def test_diff_hash(img1_path, img2_path, loops=1000):
img1 = cv2.imread(img1_path)
img2 = cv2.imread(img2_path)
start_time = time.time()
for _ in range(loops):
hash1 = dHash(img1)
hash2 = dHash(img2)
cmp_hash(hash1, hash2)
print(">>> 执行%s次耗费的时间为%.4f s." % (loops, time.time() - start_time))
def test_aHash(img1, img2):
time1 = time.time()
hash1 = aHash(img1)
hash2 = aHash(img2)
n = cmp_hash(hash1, hash2)
return concat_info("均值哈希算法", n, time.time() - time1) + "\n"
def test_dHash(img1, img2):
time1 = time.time()
hash1 = dHash(img1)
hash2 = dHash(img2)
n = cmp_hash(hash1, hash2)
return concat_info("差值哈希算法", n, time.time() - time1) + "\n"
def test_pHash(img1_path, img2_path):
time1 = time.time()
hash1 = pHash(img1_path)
hash2 = pHash(img2_path)
n = hamming_dist(hash1, hash2)
return concat_info("感知哈希算法", n, time.time() - time1) + "\n"
def deal(img1_path, img2_path):
info = ''
img1 = cv2.imread(img1_path)
img2 = cv2.imread(img2_path)
# 计算图像哈希相似度
info = info + test_aHash(img1, img2)
info = info + test_dHash(img1, img2)
info = info + test_pHash(img1_path, img2_path)
return info
def contact_path(file_name):
output_path = "./source"
return path.join(output_path, file_name)
def main():
data_img_name = 'lenna.png'
data_img_name_base = data_img_name.split(".")[0]
base = contact_path(data_img_name)
light = contact_path("%s_light.jpg" % data_img_name_base)
resize = contact_path("%s_resize.jpg" % data_img_name_base)
contrast = contact_path("%s_contrast.jpg" % data_img_name_base)
sharp = contact_path("%s_sharp.jpg" % data_img_name_base)
blur = contact_path("%s_blur.jpg" % data_img_name_base)
color = contact_path("%s_color.jpg" % data_img_name_base)
rotate = contact_path("%s_rotate.jpg" % data_img_name_base)
# 测试算法的效率
test_diff_hash(base, base)
test_diff_hash(base, light)
test_diff_hash(base, resize)
test_diff_hash(base, contrast)
test_diff_hash(base, sharp)
test_diff_hash(base, blur)
test_diff_hash(base, color)
test_diff_hash(base, rotate)
# 测试算法的精度(以base和light为例)
deal(base, light)
if __name__ == '__main__':
main()
结果:
拓展–离散余弦变换DCT
- 离散余弦变换(Discrete Cosine Transform),主要用于将数据或图像的压缩,能够将空域的信号转换 到频域上,具有良好的去相关性的性能。
- DCT变换本身是无损的,同时,由于DCT变换是对称的,所以,我们可以在量化编码后利用DCT反变 换,在接收端恢复原始的图像信息。
- DCT变换在当前的图像分析以及压缩领域有着极为广大的用途,我们常见的JPEG静态图像编码以及 MJPEG、MPEG动态编码等标准中都使用了DCT变换。
其中, - F(u,v)是输出的变换结果;
- N为原始信号的点数。
- f(i,j)是原图像中像素点(i,j)的像素值;
- c(u), c(v)是DCT系数。