使用 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

相关文章

Spring Boot3虚拟线程的使用步骤详解

《SpringBoot3虚拟线程的使用步骤详解》虚拟线程是Java19中引入的一个新特性,旨在通过简化线程管理来提升应用程序的并发性能,:本文主要介绍SpringBoot3虚拟线程的使用步骤,... 目录问题根源分析解决方案验证验证实验实验1:未启用keep-alive实验2:启用keep-alive扩展建

使用Java实现通用树形结构构建工具类

《使用Java实现通用树形结构构建工具类》这篇文章主要为大家详细介绍了如何使用Java实现通用树形结构构建工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录完整代码一、设计思想与核心功能二、核心实现原理1. 数据结构准备阶段2. 循环依赖检测算法3. 树形结构构建4. 搜索子

GORM中Model和Table的区别及使用

《GORM中Model和Table的区别及使用》Model和Table是两种与数据库表交互的核心方法,但它们的用途和行为存在著差异,本文主要介绍了GORM中Model和Table的区别及使用,具有一... 目录1. Model 的作用与特点1.1 核心用途1.2 行为特点1.3 示例China编程代码2. Tab

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

使用Python实现获取网页指定内容

《使用Python实现获取网页指定内容》在当今互联网时代,网页数据抓取是一项非常重要的技能,本文将带你从零开始学习如何使用Python获取网页中的指定内容,希望对大家有所帮助... 目录引言1. 网页抓取的基本概念2. python中的网页抓取库3. 安装必要的库4. 发送HTTP请求并获取网页内容5. 解

使用Python实现网络设备配置备份与恢复

《使用Python实现网络设备配置备份与恢复》网络设备配置备份与恢复在网络安全管理中起着至关重要的作用,本文为大家介绍了如何通过Python实现网络设备配置备份与恢复,需要的可以参考下... 目录一、网络设备配置备份与恢复的概念与重要性二、网络设备配置备份与恢复的分类三、python网络设备配置备份与恢复实

C#中的 StreamReader/StreamWriter 使用示例详解

《C#中的StreamReader/StreamWriter使用示例详解》在C#开发中,StreamReader和StreamWriter是处理文本文件的核心类,属于System.IO命名空间,本... 目录前言一、什么是 StreamReader 和 StreamWriter?1. 定义2. 特点3. 用

Python使用date模块进行日期处理的终极指南

《Python使用date模块进行日期处理的终极指南》在处理与时间相关的数据时,Python的date模块是开发者最趁手的工具之一,本文将用通俗的语言,结合真实案例,带您掌握date模块的六大核心功能... 目录引言一、date模块的核心功能1.1 日期表示1.2 日期计算1.3 日期比较二、六大常用方法详

Python使用DrissionPage中ChromiumPage进行自动化网页操作

《Python使用DrissionPage中ChromiumPage进行自动化网页操作》DrissionPage作为一款轻量级且功能强大的浏览器自动化库,为开发者提供了丰富的功能支持,本文将使用Dri... 目录前言一、ChromiumPage基础操作1.初始化Drission 和 ChromiumPage

Django序列化中SerializerMethodField的使用详解

《Django序列化中SerializerMethodField的使用详解》:本文主要介绍Django序列化中SerializerMethodField的使用,具有很好的参考价值,希望对大家有所帮... 目录SerializerMethodField的基本概念使用SerializerMethodField的