很多应用中有以图搜图的应用,那么我们应该如何实现呢?
传统的文本搜索主要是关键字匹配,而目前图片和音乐的搜索却使用使用特征向量的方式。
向量就是使用一组数从多个维度描述特征,虽然每个维度的含义我们可能无法得知,但是模型知道就足够了;
如果您的目标是快速比较两张图片的特征值,并且需要计算量较小的算法,那么使用较轻量级的模型或特征提取方法会更适合。以下是可选方案:
综合对比
- 图像哈希:适合快速比对,计算速度快,实现简单,适用于对精度要求不高的应用场景。
- 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 main
import (
"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.Y
var pixels []float32
for 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 cv2
def get_orb_features(img_path):
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
orb = cv2.ORB_create()
keypoints, descriptors = orb.detectAndCompute(img, None)
return descriptors
def 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 main
import (
"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())
// 创建 BFMatcher
bf := opencv.NewBFMatcher()
defer bf.Release()
// 匹配特征描述符
matches := bf.KnnMatch(desc1, desc2, 2)
// 筛选最佳匹配
var goodMatches []opencv.DMatch
for _, 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 imagehash
def 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 aHash
similarity = 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 main
import (
"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)
}
不同哈希算法对比
-
平均哈希 (aHash):
-
步骤:
- 缩小图像尺寸到 8x8。
-
-
转为灰度图。
3. 计算像素平均值。
4. 每个像素值与平均值比较,生成哈希值。- 特点: 计算过程简单快捷,适合快速图像比对。
-
感知哈希 (pHash):
-
步骤:
- 缩小图像尺寸到 32x32。
-
-
转为灰度图。
3. 进行离散余弦变换 (DCT)。
4. 取左上角 8x8 的 DCT 系数,计算平均值。
5. 每个系数与平均值比较,生成哈希值。- 特点: 更复杂,能捕捉更多图像的感知特征,计算速度相对较慢,但鲁棒性更好。
-
差值哈希 (dHash):
-
步骤:
- 缩小图像尺寸到 9x8。
-
-
转为灰度图。
3. 每行比较相邻像素,生成哈希值。- 特点: 计算过程简单快捷,适合快速图像比对。
总结
- aHash 和 dHash 通常计算速度相对较快,适用于对速度要求较高的应用场景。
- 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 - hash2
max_distance = len(hash1.hash) ** 2
similarity = 1 - (hamming_distance / max_distance)
return similarity
def 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, descriptors
def 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 matches
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()
# 初步哈希筛选
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_array
def 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
当然还要区分照片和真实人脸,可以考虑以下几种方法:
- 活体检测(Liveness Detection):活体检测是一种用于验证被扫描对象是否为真人的技术。这可以通过多种方式实现,例如检测人脸的微小运动(比如眨眼或者张嘴)、检测面部纹理和深度信息等。你可以使用活体检测技术来区分静态照片和真实的人脸。
- 光线反射检测:利用摄像头的光线反射特性来检测照片和真实人脸之间的区别。真实人脸会在不同光线条件下产生微小的反射变化,而静态照片则不会。
- 红外光检测:使用红外摄像头来捕捉面部的热量分布。真实人脸和照片在红外光下会有不同的反应,可以通过检测这些差异来区分它们。
- 3D深度检测:使用具有深度感知功能的摄像头或者传感器来获取人脸的三维结构信息。静态照片通常无法提供足够的深度信息,因此可以利用这一点来区分照片和真实人脸。
- 多模态验证:结合多种不同的检测方法,例如结合活体检测和光线反射检测,以提高准确性和安全性。
这些方法通常会结合在一起,构成一个完整的人脸识别系统,确保门禁识别系统能够有效地区分照片和真实人脸,提高安全性和可靠性。
光线反射检测的方法通常利用摄像头捕捉的图像中的光线反射特征来区分真实人脸和静态照片。以下是一个使用 OpenCV 库来实现简单光线反射检测的 Python 代码示例:
import cv2
def 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 算子来计算图像的梯度,然后根据梯度的方差(变化程度)来判断是否存在光线反射。如果梯度较小,就可能是静态照片;如果梯度较大,则可能是真实人脸。你可以根据实际情况调整阈值来提高准确性。
完。