Commit 5712ddc8 authored by shenyong's avatar shenyong

优化相似度判断

parent 0f7aa68e
...@@ -55,6 +55,9 @@ class PhotoAndVideoMananger { ...@@ -55,6 +55,9 @@ class PhotoAndVideoMananger {
var ids:[String] = [] var ids:[String] = []
// 定义
private let hashDistance = 100
func setAssets() { func setAssets() {
let fetchOptions = PHFetchOptions() let fetchOptions = PHFetchOptions()
...@@ -1009,66 +1012,83 @@ class PhotoAndVideoMananger { ...@@ -1009,66 +1012,83 @@ class PhotoAndVideoMananger {
// 计算两个哈希值的汉明距离 // 计算两个哈希值的汉明距离
func hammingDistance(_ hash1: String, _ hash2: String) -> Int { func hammingDistance(_ hash1: String, _ hash2: String) -> Int {
var distance = 0 // var distance = 0
for (char1, char2) in zip(hash1, hash2) { // for (char1, char2) in zip(hash1, hash2) {
let int1 = Int(String(char1), radix: 16)! // let int1 = Int(String(char1), radix: 16)!
let int2 = Int(String(char2), radix: 16)! // let int2 = Int(String(char2), radix: 16)!
let xor = int1 ^ int2 // let xor = int1 ^ int2
distance += String(xor, radix: 2).filter { $0 == "1" }.count // distance += String(xor, radix: 2).filter { $0 == "1" }.count
} // }
return distance // return distance
guard hash1.count == hash2.count else { return Int.max }
return zip(hash1, hash2).filter { $0 != $1 }.count
} }
func groupSimilarImages(assets: [PHAsset], progressCompletion: @escaping ([[AssetModel]]) -> Void, completion: @escaping ([[AssetModel]]) -> Void) { func groupSimilarImages(assets: [PHAsset], progressCompletion: @escaping ([[AssetModel]]) -> Void, completion: @escaping ([[AssetModel]]) -> Void) {
print("开始处理相似图片")
DispatchQueue.global().async { DispatchQueue.global().async {
print("进入异步任务处理相似图片")
var assetModels: [AssetModel] = [] var assetModels: [AssetModel] = []
var hashes: [String: AssetModel] = [:] var hashes: [String: AssetModel] = [:]
var groupedImages: [[AssetModel]] = [] var groupedImages: [[AssetModel]] = []
let dispatchGroup = DispatchGroup()
for asset in assets { for asset in assets {
_ = asset.pixelWidth * asset.pixelHeight // 创建 AssetModel
let createDate = asset.creationDate ?? Date() let createDate = asset.creationDate ?? Date()
let model = AssetModel(localIdentifier: asset.localIdentifier, assetSize: self.findAssetSize(asset: asset), createDate: createDate) let model = AssetModel(localIdentifier: asset.localIdentifier, assetSize: self.findAssetSize(asset: asset), createDate: createDate)
assetModels.append(model) assetModels.append(model)
let manager = PHImageManager.default() // 请求图像
manager.requestImage(for: asset, targetSize: CGSize(width: 32, height: 32), contentMode: .aspectFit, options: nil) { (image, _) in dispatchGroup.enter()
PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 32, height: 32), contentMode: .aspectFit, options: nil) { (image, _) in
if let image = image { if let image = image {
let hash = self.pHash(for: image) let hash = self.calculateImageHashUsingCoreImage(image)
if hash != "" { if !hash.isEmpty {
hashes[hash] = model hashes[hash] = model
} }
} }
dispatchGroup.leave()
} }
} }
var usedHashes: Set<String> = [] // 等待所有请求完成后进行比较
for (hash1, model1) in hashes { dispatchGroup.notify(queue: .global()) {
if usedHashes.contains(hash1) { continue } print("获取到全部hash值")
var similarModels: [AssetModel] = [model1]
usedHashes.insert(hash1) var usedHashes: Set<String> = []
for (hash1, model1) in hashes {
for (hash2, model2) in hashes { if usedHashes.contains(hash1) { continue }
if usedHashes.contains(hash2) { continue } var similarModels: [AssetModel] = [model1]
let distance = self.hammingDistance(hash1, hash2) usedHashes.insert(hash1)
if distance < 4 { // 汉明距离小于 5 认为相似
similarModels.append(model2) for (hash2, model2) in hashes {
usedHashes.insert(hash2) if usedHashes.contains(hash2) { continue }
let distance = self.hammingDistance(hash1, hash2)
if distance < self.hashDistance { // 可以根据需求调整阈值
similarModels.append(model2)
usedHashes.insert(hash2)
}
}
if similarModels.count >= 2 {
groupedImages.append(similarModels)
// 每次找到新的相似组,通过 progressCompletion 回调返回当前已处理好的分组数据
print("判断相似", similarModels)
DispatchQueue.main.async {
progressCompletion(groupedImages)
}
} }
} }
if similarModels.count >= 2 { DispatchQueue.main.async {
groupedImages.append(similarModels) completion(groupedImages)
// 每次找到新的相似组,通过 progressCompletion 回调返回当前已处理好的分组数据
DispatchQueue.main.async {
progressCompletion(groupedImages)
}
} }
} }
DispatchQueue.main.async {
completion(groupedImages)
}
} }
} }
} }
...@@ -1148,3 +1168,69 @@ extension Array { ...@@ -1148,3 +1168,69 @@ extension Array {
} }
} }
} }
extension PhotoAndVideoMananger{
// 计算图片的感知哈希值
func calculateImageHashUsingCoreImage(_ image: UIImage) -> String {
guard let cgImage = image.cgImage else { return "" }
// 生成CIImage
let ciImage = CIImage(cgImage: cgImage)
// 创建滤镜:灰度化图像
let filter = CIFilter(name: "CIPhotoEffectNoir")!
filter.setValue(ciImage, forKey: kCIInputImageKey)
guard let outputImage = filter.outputImage else {
return ""
}
// 将输出图像缩放到32x32
let context = CIContext()
let targetSize = CGSize(width: 32, height: 32)
let scaledImage = context.createCGImage(outputImage, from: outputImage.extent)!
let resizedImage = UIImage(cgImage: scaledImage, scale: 1.0, orientation: image.imageOrientation)
// 获取像素数据
guard let pixelData = resizedImage.cgImage?.dataProvider?.data,
let data = CFDataGetBytePtr(pixelData) else {
return ""
}
// 计算灰度像素平均值
var pixels: [UInt8] = Array(repeating: 0, count: 1024) // 32 * 32
for i in 0..<32 {
for j in 0..<32 {
let pixelIndex = (i * 32 + j) * 4 // RGBA
let r = data[pixelIndex]
let g = data[pixelIndex + 1]
let b = data[pixelIndex + 2]
// 使用灰度公式转化
let gray = UInt8(0.299 * Double(r) + 0.587 * Double(g) + 0.114 * Double(b))
pixels[i * 32 + j] = gray
}
}
// 计算平均值
let sum = pixels.reduce(0) { UInt32($0) + UInt32($1) } // 使用 UInt32 来避免溢出
let average = UInt8(sum / UInt32(pixels.count)) // 确保将 sum 转换为 UInt32
// 生成哈希值
var hash = ""
for pixel in pixels {
hash += pixel > average ? "1" : "0"
}
return hash
}
// 计算汉明距离
func calculateHammingDistance(_ hash1: String, _ hash2: String) -> Int {
guard hash1.count == hash2.count else { return Int.max }
return zip(hash1, hash2).filter { $0 != $1 }.count
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment