使用 MobileNet和ImageHash做图片相似度匹配(以图搜图)

2024-06-02 12:12

本文主要是介绍使用 MobileNet和ImageHash做图片相似度匹配(以图搜图),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

很多应用中有以图搜图的应用,那么我们应该如何实现呢?

传统的文本搜索主要是关键字匹配,而目前图片和音乐的搜索却使用使用特征向量的方式。
向量就是使用一组数从多个维度描述特征,虽然每个维度的含义我们可能无法得知,但是模型知道就足够了;

如果您的目标是快速比较两张图片的特征值,并且需要计算量较小的算法,那么使用较轻量级的模型或特征提取方法会更适合。以下是可选方案:

综合对比

  • 图像哈希:适合快速比对,计算速度快,实现简单,适用于对精度要求不高的应用场景。
  • ORB 特征:适合捕捉局部特征,计算速度快,适用于实时应用和动态场景,对光照和视角变化敏感。
  • MobileNet:高精度和鲁棒性,适合对精度要求高的应用场景,计算资源需求较高。

1. 使用 MobileNet

MobileNet 是一个轻量级的卷积神经网络,设计用于在移动和嵌入式设备上高效运行。与 VGG16 相比,它更快且计算量更小。

MobileNet 是一种轻量级的深度神经网络模型,由 Google 在 2017 年提出。它主要用于图像分类和特征提取任务,并且相比于传统的深度神经网络模型,MobileNet 具有更小的模型尺寸和更低的计算成本,适用于移动设备等资源受限的环境。

MobileNet 的设计思想是通过深度可分离卷积(Depthwise Separable Convolution)来减少模型参数和计算量。深度可分离卷积将标准卷积分解为两个步骤:深度卷积(Depthwise Convolution)和逐点卷积(Pointwise Convolution)。深度卷积对每个输入通道进行单独卷积操作,而逐点卷积则对深度卷积的结果进行 1x1 卷积操作,以融合不同通道的信息。

MobileNet 的结构比较简单,由若干个深度可分离卷积层和激活函数层组成,最后通过全局平均池化(Global Average Pooling)将特征图转换为固定长度的特征向量。该特征向量可以用于图像分类、相似度计算等任务。

MobileNet 通过在 ImageNet 数据集上进行预训练,可以提取图像中的语义信息,并用于各种计算机视觉任务。在 TensorFlow 中,可以通过使用 tensorflow.keras.applications.MobileNet 类加载预训练的 MobileNet 模型,并在自己的任务中使用它。

这里我测试了两张一样的图片,其中一张的缩小的版本:

import tensorflow as tf
from tensorflow.keras.applications import MobileNet
from tensorflow.keras.applications.mobilenet import preprocess_input
from tensorflow.keras.preprocessing import image
import numpy as np
from PIL import Image# 加载预训练的MobileNet模型,不包括顶部的全连接层
model = MobileNet(weights='imagenet', include_top=False, pooling='avg')def get_image_feature_vector(img_path):img = Image.open(img_path)img = img.resize((224, 224))x = image.img_to_array(img)x = np.expand_dims(x, axis=0)x = preprocess_input(x)features = model.predict(x)return features.flatten()# 获取两张图片的特征向量
features1 = get_image_feature_vector('image/1.jpg')
features2 = get_image_feature_vector('image/2.jpg')# 计算余弦相似度
similarity = np.dot(features1, features2) / (np.linalg.norm(features1) * np.linalg.norm(features2))
print("Similarity:", similarity)# Similarity: 0.99791807

GO版本

package mainimport ("fmt""log""math"tf "github.com/tensorflow/tensorflow/tensorflow/go""github.com/disintegration/imaging"
)func main() {// 加载预训练的 MobileNet 模型model, err := tf.LoadSavedModel("mobilenet_saved_model", []string{"serve"}, nil)if err != nil {log.Fatalf("无法加载模型: %v", err)}defer model.Session.Close()// 加载并预处理图像img1, err := loadImageAndPreprocess("image/1.jpg")if err != nil {log.Fatalf("无法加载图像: %v", err)}img2, err := loadImageAndPreprocess("image/2.jpg")if err != nil {log.Fatalf("无法加载图像: %v", err)}// 获取图像的特征向量features1, err := extractImageFeatureVector(model, img1)if err != nil {log.Fatalf("无法提取特征向量: %v", err)}features2, err := extractImageFeatureVector(model, img2)if err != nil {log.Fatalf("无法提取特征向量: %v", err)}// 计算余弦相似度similarity := cosineSimilarity(features1, features2)fmt.Println("Similarity:", similarity)
}// loadImageAndPreprocess 加载图像并进行预处理
func loadImageAndPreprocess(imgPath string) (*tf.Tensor, error) {img, err := imaging.Open(imgPath)if err != nil {return nil, fmt.Errorf("无法打开图像: %v", err)}img = imaging.Resize(img, 224, 224, imaging.Lanczos)preprocessedImg := preprocessImage(img)return tensorFromImage(preprocessedImg), nil
}// preprocessImage 对图像进行预处理
func preprocessImage(img image.Image) image.Image {// 在这里添加预处理逻辑,如归一化、标准化等return img
}// tensorFromImage 从图像创建 TensorFlow 张量
func tensorFromImage(img image.Image) *tf.Tensor {bounds := img.Bounds()width, height := bounds.Max.X, bounds.Max.Yvar pixels []float32for y := 0; y < height; y++ {for x := 0; x < width; x++ {r, g, b, _ := img.At(x, y).RGBA()pixels = append(pixels, float32(r>>8), float32(g>>8), float32(b>>8))}}return tf.NewTensor(pixels)
}// extractImageFeatureVector 提取图像的特征向量
func extractImageFeatureVector(model *tf.SavedModel, img *tf.Tensor) (*tf.Tensor, error) {output, err := model.Session.Run(map[tf.Output]*tf.Tensor{model.Graph.Operation("input_1").Output(0): img,},[]tf.Output{model.Graph.Operation("global_average_pooling2d/Mean").Output(0),},nil,)if err != nil {return nil, fmt.Errorf("无法提取特征向量: %v", err)}return output[0], nil
}// cosineSimilarity 计算余弦相似度
func cosineSimilarity(features1, features2 *tf.Tensor) float32 {numerator, err := tf.MatMul(features1, features2, false, true)if err != nil {log.Fatalf("无法计算余弦相似度的分子: %v", err)}denominator := tf.Norm(features1, 2).Mul(tf.Norm(features2, 2))similarity := numerator.Reshape([]int32{}).Value().(float32) / denominator.Reshape([]int32{}).Value().(float32)return similarity
}

备注:MobileNet 是一种轻量级的深度卷积神经网络,设计用于在资源受限的设备上运行,适合提取图像的全局特征。

优点:

  • 高精度:MobileNet 提取的特征向量具有较高的表示能力,适用于精确的图像比对。
  • 对变形鲁棒:对图像的光照、视角变化具有更好的鲁棒性。
  • 预训练模型:可以利用在大规模数据集(如 ImageNet)上预训练的模型,具有较好的泛化能力。

缺点:

  • 需要预训练:需要加载预训练模型,初始加载时间较长。
  • 计算资源需求较高:虽然比其他大型模型轻量,但相比 ORB,计算资源需求仍然较高。

2. 使用 ORB 特征

ORB (Oriented FAST and Rotated BRIEF) 是一种用于特征提取和匹配的快速算法,适用于图像的快速比对。

import cv2def get_orb_features(img_path):img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)orb = cv2.ORB_create()keypoints, descriptors = orb.detectAndCompute(img, None)return descriptorsdef compare_images(img_path1, img_path2):des1 = get_orb_features(img_path1)des2 = get_orb_features(img_path2)bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)matches = bf.match(des1, des2)matches = sorted(matches, key=lambda x: x.distance)return len(matches)# 比较两张图片
num_matches = compare_images('image/1.jpg', 'image/2.jpg')
print("Number of ORB matches:", num_matches)#ORB Similarity: 0.22

同时给出go版本

package mainimport ("fmt""log""github.com/lazywei/go-opencv/opencv"
)func main() {// 读取图像img1 := opencv.LoadImage("image/1.jpg", 0)img2 := opencv.LoadImage("image/2.jpg", 0)if img1 == nil || img2 == nil {log.Fatal("无法加载图像")}defer img1.Release()defer img2.Release()// 创建 ORB 特征检测器orb := opencv.NewORB()defer orb.Release()// 在图像上检测特征点和计算特征描述符kp1, desc1 := orb.DetectAndCompute(img1, opencv.NewMat())kp2, desc2 := orb.DetectAndCompute(img2, opencv.NewMat())// 创建 BFMatcherbf := opencv.NewBFMatcher()defer bf.Release()// 匹配特征描述符matches := bf.KnnMatch(desc1, desc2, 2)// 筛选最佳匹配var goodMatches []opencv.DMatchfor _, m := range matches {if len(m) == 2 && m[0].Distance < 0.75*m[1].Distance {goodMatches = append(goodMatches, m[0])}}// 输出相似度similarity := float64(len(goodMatches)) / float64(len(kp1))fmt.Println("ORB Similarity:", similarity)
}

对比

  • ORB 特征:适合快速、实时的图像比对,不需要预训练模型,对计算资源要求较低,适用于对精度要求不高的场景。
  • MobileNet:适合需要高精度和鲁棒性的图像比对,提取的特征具有较好的表示能力,适用于对计算资源要求不太敏感的场景。

选择具体的方法应根据实际需求和应用场景决定。如果需要在移动设备或嵌入式设备上运行,且对计算速度要求较高,可以选择 ORB。如果在服务器环境中运行,且对图像比对精度要求较高,可以选择 MobileNet。

3. 使用图片哈希算法

图像感知哈希算法(如差值哈希、感知哈希)可以快速计算图像的哈希值,并比较这些哈希值以确定图像相似度。

图像哈希 (Image Hashing)

优点:

  • 速度快:计算哈希值的过程非常快,适合快速比对大规模图像。
  • 实现简单:算法简单易实现,代码量少,适合初学者和快速开发。
  • 适用于静态图像:对图像的旋转、缩放等变换有一定的鲁棒性。

缺点:

  • 精度有限:对图像内容的细微变化(如光照变化、视角变化等)不敏感,无法处理复杂的图像变换。
  • 局部特征不足:哈希值通常表示全局特征,难以捕捉局部变化。
from PIL import Image
import imagehashdef get_image_hash(img_path):img = Image.open(img_path)return imagehash.average_hash(img)def calculate_similarity(hash1, hash2):# 计算哈希值的汉明距离hamming_distance = hash1 - hash2# 将汉明距离转换为相似度(距离越小,相似度越高)max_distance = len(hash1.hash) ** 2  # 64 for aHashsimilarity = 1 - (hamming_distance / max_distance)return similarity# 获取两张图片的哈希值
hash1 = get_image_hash('image/1.jpg')
hash2 = get_image_hash('image/2.jpg')# 计算相似度
similarity = calculate_similarity(hash1, hash2)
print("Similarity (1 is identical, 0 is completely different):", similarity)

这些方法在计算速度和资源消耗方面比 VGG16 更加高效,可以满足快速比对图片特征值的需求。根据具体应用场景选择适合的方法即可。

package mainimport ("fmt""log""github.com/corona10/goimagehash""github.com/disintegration/imaging"
)func main() {// 打开图像文件img1, err := imaging.Open("image/1.jpg")if err != nil {log.Fatalf("无法打开图像: %v", err)}img2, err := imaging.Open("image/2.jpg")if err != nil {log.Fatalf("无法打开图像: %v", err)}// 计算图像的哈希值hash1, err := goimagehash.AverageHash(img1)if err != nil {log.Fatalf("无法计算哈希值: %v", err)}hash2, err := goimagehash.AverageHash(img2)if err != nil {log.Fatalf("无法计算哈希值: %v", err)}// 计算汉明距离并转换为相似度hammingDistance := hash1.Distance(hash2)maxDistance := len(hash1) * len(hash1)similarity := 1.0 - (float64(hammingDistance) / float64(maxDistance))fmt.Println("Similarity (1 is identical, 0 is completely different):", similarity)
}

不同哈希算法对比

  1. 平均哈希 (aHash):

    • 步骤:

      1. 缩小图像尺寸到 8x8。
  2. 转为灰度图。
    3. 计算像素平均值。
    4. 每个像素值与平均值比较,生成哈希值。

    • 特点: 计算过程简单快捷,适合快速图像比对。
  3. 感知哈希 (pHash):

    • 步骤:

      1. 缩小图像尺寸到 32x32。
  4. 转为灰度图。
    3. 进行离散余弦变换 (DCT)。
    4. 取左上角 8x8 的 DCT 系数,计算平均值。
    5. 每个系数与平均值比较,生成哈希值。

    • 特点: 更复杂,能捕捉更多图像的感知特征,计算速度相对较慢,但鲁棒性更好。
  5. 差值哈希 (dHash):

    • 步骤:

      1. 缩小图像尺寸到 9x8。
  6. 转为灰度图。
    3. 每行比较相邻像素,生成哈希值。

    • 特点: 计算过程简单快捷,适合快速图像比对。

总结

  • aHashdHash 通常计算速度相对较快,适用于对速度要求较高的应用场景。
  • pHash 虽然计算过程稍复杂,但能捕捉更多图像的感知特征,适用于对鲁棒性要求更高的应用场景。

4. 以图搜图

我们使用图哈希以图搜图是否能实现需求呢,其实不一定,哈希实现的精度低,可以粗略的确定范围,
再使用MobileNet再次比对,实现分级的搜索,这里测试一下:

分级对比:

from PIL import Image
import imagehash
import cv2
import numpy as np
import tensorflow as tf
from tensorflow.keras.applications import MobileNet
from tensorflow.keras.applications.mobilenet import preprocess_input
from tensorflow.keras.preprocessing import image# 加载预训练的MobileNet模型,不包括顶部的全连接层
model = MobileNet(weights='imagenet', include_top=False, pooling='avg')def get_image_hash(img_path, hash_function=imagehash.average_hash):img = Image.open(img_path)return hash_function(img)def calculate_hash_similarity(hash1, hash2):hamming_distance = hash1 - hash2max_distance = len(hash1.hash) ** 2similarity = 1 - (hamming_distance / max_distance)return similaritydef get_orb_features(img_path):img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)orb = cv2.ORB_create()keypoints, descriptors = orb.detectAndCompute(img, None)return keypoints, descriptorsdef match_orb_features(descriptors1, descriptors2):bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)matches = bf.match(descriptors1, descriptors2)matches = sorted(matches, key=lambda x: x.distance)return matchesdef get_image_feature_vector(img_path):img = Image.open(img_path)img = img.resize((224, 224))x = image.img_to_array(img)x = np.expand_dims(x, axis=0)x = preprocess_input(x)features = model.predict(x)return features.flatten()# 初步哈希筛选
hash1 = get_image_hash('image/1.jpg')
hash2 = get_image_hash('image/2.jpg')
hash_similarity = calculate_hash_similarity(hash1, hash2)
print("Hash Similarity:", hash_similarity)if hash_similarity > 0.8:  # 设置一个阈值进行初步筛选# ORB特征匹配keypoints1, descriptors1 = get_orb_features('image/1.jpg')keypoints2, descriptors2 = get_orb_features('image/2.jpg')matches = match_orb_features(descriptors1, descriptors2)orb_similarity = len(matches) / min(len(descriptors1), len(descriptors2))print("ORB Similarity:", orb_similarity)# MobileNet特征比对features1 = get_image_feature_vector('image/1.jpg')features2 = get_image_feature_vector('image/2.jpg')mobile_similarity = np.dot(features1, features2) / (np.linalg.norm(features1) * np.linalg.norm(features2))print("MobileNet Similarity:", mobile_similarity)
else:print("Images are not similar enough based on hash comparison.")

5、图像分类

MobileNet 可以用于物体分类任务。MobileNet 在 ImageNet 数据集上进行了预训练,其中包含了 1000 种不同类别的物体。因此,你可以使用 MobileNet 模型对图像中的物体进行分类。

你可以使用 MobileNet 模型提取图像的特征向量,然后将这些特征向量输入到一个分类器中进行分类。在 TensorFlow 中,你可以通过加载 MobileNet 模型并在顶部添加一个分类器来实现这一点。这个分类器可以是一个全连接层或者其他类型的分类器,具体取决于你的应用需求。

除了在 ImageNet 上进行预训练的 MobileNet 外,你也可以针对特定的物体分类任务对 MobileNet 进行微调(fine-tuning),以适应你的数据集和任务要求。这样可以提高模型在特定物体分类任务上的性能和准确度。

import tensorflow as tf
import numpy as np
from tensorflow.keras.applications import MobileNet
from tensorflow.keras.applications.mobilenet import preprocess_input 
from tensorflow.keras.applications.mobilenet import decode_predictions
from tensorflow.keras.preprocessing import image# 加载预训练的 MobileNet 模型
model = MobileNet(weights='imagenet')def preprocess_image(img_path):# 加载图像并调整大小img = image.load_img(img_path, target_size=(224, 224))# 将图像转换为 NumPy 数组img_array = image.img_to_array(img)# 扩展数组的维度以匹配模型的输入要求img_array = np.expand_dims(img_array, axis=0)# 预处理图像img_array = preprocess_input(img_array)return img_arraydef classify_image(img_path):# 预处理图像img_array = preprocess_image(img_path)# 使用 MobileNet 模型进行分类preds = model.predict(img_array)# 解码预测结果(获取类别标签)decoded_preds = decode_predictions(preds, top=1)[0]# 返回预测的类别和概率return decoded_preds# 测试图像分类
img_path = './image/dog1.jpg'
predicted_class = classify_image(img_path)
print(f'Predicted Class: {predicted_class}')# Predicted Class: [('n02099601', 'golden_retriever', 0.93234175)]
# 金毛

在这里插入图片描述

其中的1000种文件分类的列表,
可以在 ImageNet 官方网站上找到标签文件。
ImageNet 官方网站:ImageNet

当然还要区分照片和真实人脸,可以考虑以下几种方法:

  1. 活体检测(Liveness Detection):活体检测是一种用于验证被扫描对象是否为真人的技术。这可以通过多种方式实现,例如检测人脸的微小运动(比如眨眼或者张嘴)、检测面部纹理和深度信息等。你可以使用活体检测技术来区分静态照片和真实的人脸。
  2. 光线反射检测:利用摄像头的光线反射特性来检测照片和真实人脸之间的区别。真实人脸会在不同光线条件下产生微小的反射变化,而静态照片则不会。
  3. 红外光检测:使用红外摄像头来捕捉面部的热量分布。真实人脸和照片在红外光下会有不同的反应,可以通过检测这些差异来区分它们。
  4. 3D深度检测:使用具有深度感知功能的摄像头或者传感器来获取人脸的三维结构信息。静态照片通常无法提供足够的深度信息,因此可以利用这一点来区分照片和真实人脸。
  5. 多模态验证:结合多种不同的检测方法,例如结合活体检测和光线反射检测,以提高准确性和安全性。

这些方法通常会结合在一起,构成一个完整的人脸识别系统,确保门禁识别系统能够有效地区分照片和真实人脸,提高安全性和可靠性。

光线反射检测的方法通常利用摄像头捕捉的图像中的光线反射特征来区分真实人脸和静态照片。以下是一个使用 OpenCV 库来实现简单光线反射检测的 Python 代码示例:

import cv2def detect_reflection(image_path):# 读取图像image = cv2.imread(image_path)# 转换为灰度图像gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 计算灰度图像的梯度gradient = cv2.Laplacian(gray, cv2.CV_64F).var()# 根据梯度阈值进行判断threshold = 100  # 调整阈值以适应不同情况if gradient < threshold:print("照片")else:print("真实人脸")# 在这里替换为你的图像路径
image_path = "path/to/your/image.jpg"
detect_reflection(image_path)

这个代码示例使用 Laplacian 算子来计算图像的梯度,然后根据梯度的方差(变化程度)来判断是否存在光线反射。如果梯度较小,就可能是静态照片;如果梯度较大,则可能是真实人脸。你可以根据实际情况调整阈值来提高准确性。

完。

这篇关于使用 MobileNet和ImageHash做图片相似度匹配(以图搜图)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1023930

相关文章

postgresql使用UUID函数的方法

《postgresql使用UUID函数的方法》本文给大家介绍postgresql使用UUID函数的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录PostgreSQL有两种生成uuid的方法。可以先通过sql查看是否已安装扩展函数,和可以安装的扩展函数

如何使用Lombok进行spring 注入

《如何使用Lombok进行spring注入》本文介绍如何用Lombok简化Spring注入,推荐优先使用setter注入,通过注解自动生成getter/setter及构造器,减少冗余代码,提升开发效... Lombok为了开发环境简化代码,好处不用多说。spring 注入方式为2种,构造器注入和setter

MySQL中比较运算符的具体使用

《MySQL中比较运算符的具体使用》本文介绍了SQL中常用的符号类型和非符号类型运算符,符号类型运算符包括等于(=)、安全等于(=)、不等于(/!=)、大小比较(,=,,=)等,感兴趣的可以了解一下... 目录符号类型运算符1. 等于运算符=2. 安全等于运算符<=>3. 不等于运算符<>或!=4. 小于运

使用zip4j实现Java中的ZIP文件加密压缩的操作方法

《使用zip4j实现Java中的ZIP文件加密压缩的操作方法》本文介绍如何通过Maven集成zip4j1.3.2库创建带密码保护的ZIP文件,涵盖依赖配置、代码示例及加密原理,确保数据安全性,感兴趣的... 目录1. zip4j库介绍和版本1.1 zip4j库概述1.2 zip4j的版本演变1.3 zip4

Python 字典 (Dictionary)使用详解

《Python字典(Dictionary)使用详解》字典是python中最重要,最常用的数据结构之一,它提供了高效的键值对存储和查找能力,:本文主要介绍Python字典(Dictionary)... 目录字典1.基本特性2.创建字典3.访问元素4.修改字典5.删除元素6.字典遍历7.字典的高级特性默认字典

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

一文详解如何使用Java获取PDF页面信息

《一文详解如何使用Java获取PDF页面信息》了解PDF页面属性是我们在处理文档、内容提取、打印设置或页面重组等任务时不可或缺的一环,下面我们就来看看如何使用Java语言获取这些信息吧... 目录引言一、安装和引入PDF处理库引入依赖二、获取 PDF 页数三、获取页面尺寸(宽高)四、获取页面旋转角度五、判断

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

使用Python删除Excel中的行列和单元格示例详解

《使用Python删除Excel中的行列和单元格示例详解》在处理Excel数据时,删除不需要的行、列或单元格是一项常见且必要的操作,本文将使用Python脚本实现对Excel表格的高效自动化处理,感兴... 目录开发环境准备使用 python 删除 Excphpel 表格中的行删除特定行删除空白行删除含指定