| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260 |
- import Photos
- import Vision
- class ClassifyPhoto {
-
- struct PhotoSizeInfo {
- var totalSize: Int64 = 0
- var count: Int = 0
- }
- struct ClassifiedPhotos {
- var screenshots: [PHAsset] = []
- var locations: [String: [PHAsset]] = [:] // 按地点分组
- var people: [String: [PHAsset]] = [:] // 按人物分组
- var similarPhotos: [[PHAsset]] = [] // 存储相似照片组
- var blurryPhotos: [PHAsset] = [] // 添加模糊照片数组
-
- // 添加容量信息
- var screenshotsSize: PhotoSizeInfo = PhotoSizeInfo()
- var locationsSize: PhotoSizeInfo = PhotoSizeInfo()
- var peopleSize: PhotoSizeInfo = PhotoSizeInfo()
- var similarPhotosSize: PhotoSizeInfo = PhotoSizeInfo()
- var blurryPhotosSize: PhotoSizeInfo = PhotoSizeInfo() // 添加模糊照片容量信息
- }
-
- // 添加位置缓存
- private var locationCache: [String: String] = [:]
- func classifyPhotos(
- assets: PHFetchResult<PHAsset>,
- progressHandler: @escaping (String, Float) -> Void,
- completion: @escaping (ClassifiedPhotos) -> Void
- ) {
-
- // 在后台队列处理
- DispatchQueue.global(qos: .userInitiated).async {
- var result = ClassifiedPhotos()
- let group = DispatchGroup()
-
- // 开始处理
- DispatchQueue.main.async {
- progressHandler("正在加载照片...", 0.0)
- }
-
- // 先处理模糊照片检测(占进度的 30%)
- group.enter()
- progressHandler("正在检测模糊照片...", 0.0)
- self.detectBlurryPhotos(from: assets) { blurryPhotos in
- result.blurryPhotos = blurryPhotos
- progressHandler("模糊照片检测完成", 0.3)
- group.leave()
- }
-
- // 1. 检测截图 (占总进度的 20%)
- group.enter()
- self.fetchScreenshots(from: assets) { screenshots in
- result.screenshots = screenshots
- DispatchQueue.main.async {
- progressHandler("正在检测截图...", 0.3)
- }
- group.leave()
- }
-
- // 2. 检测相似照片 (占总进度的 80%)
- group.enter()
- self.detectSimilarPhotos(
- assets: assets,
- progressHandler: { stage, progress in
- // 将相似照片检测的进度映射到 20%-100% 的范围
- let mappedProgress = 0.3 + (progress * 0.6)
- DispatchQueue.main.async {
- progressHandler(stage, mappedProgress)
- }
- }
- ) { similarPhotos in
- result.similarPhotos = similarPhotos
- group.leave()
- }
-
- // 3. 按地点分类 (占总进度的 20%)
- // group.enter()
- // self.classifyByLocation(assets: assets) { locationGroups in
- // result.locations = locationGroups
- // DispatchQueue.main.async {
- // progressHandler("正在按地点分类...", 0.8)
- // }
- // group.leave()
- // }
-
- // 4. 按人物分类 (占总进度的 20%)
- group.enter()
- self.classifyByPeople(assets: assets) { peopleGroups in
- result.people = peopleGroups
- DispatchQueue.main.async {
- progressHandler("正在按人物分类...", 1.0)
- }
- group.leave()
- }
-
- // // 添加模糊照片检测
- // group.enter()
- // self.detectBlurryPhotos(from: assets) { blurryPhotos in
- // result.blurryPhotos = blurryPhotos
- // DispatchQueue.main.async {
- // progressHandler("正在检测模糊照片...", 1.0)
- // }
- // group.leave()
- // }
-
- // 在所有分类完成后计算大小
- group.notify(queue: .main) {
- let sizeGroup = DispatchGroup()
-
- // 计算模糊照片大小
- sizeGroup.enter()
- self.calculateAssetsSize(result.blurryPhotos) { sizeInfo in
- result.blurryPhotosSize = sizeInfo
- sizeGroup.leave()
- }
-
-
- // 计算相似照片大小
- sizeGroup.enter()
- let similarAssets = Array(result.similarPhotos.flatMap { $0 })
- self.calculateAssetsSize(similarAssets) { sizeInfo in
- result.similarPhotosSize = sizeInfo
- sizeGroup.leave()
- }
-
- // 计算截图大小
- sizeGroup.enter()
- self.calculateAssetsSize(result.screenshots) { sizeInfo in
- result.screenshotsSize = sizeInfo
- sizeGroup.leave()
- }
-
- // // 计算地点照片大小
- // sizeGroup.enter()
- // let locationAssets = Array(result.locations.values.flatMap { $0 })
- // self.calculateAssetsSize(locationAssets) { sizeInfo in
- // result.locationsSize = sizeInfo
- // sizeGroup.leave()
- // }
-
- // 计算人物照片大小
- sizeGroup.enter()
- let peopleAssets = Array(result.people.values.flatMap { $0 })
- self.calculateAssetsSize(peopleAssets) { sizeInfo in
- result.peopleSize = sizeInfo
- sizeGroup.leave()
- }
-
- // 所有大小计算完成后回调
- sizeGroup.notify(queue: .main) {
- progressHandler("分类完成", 1.0)
- completion(result)
- }
- }
- }
- }
-
- // 添加内存清理辅助方法
- private func cleanupMemory() {
- // 清理图像缓存
- URLCache.shared.removeAllCachedResponses()
-
- // 强制进行一次垃圾回收
- autoreleasepool {
- let _ = [String](repeating: "temp", count: 1)
- }
-
- #if os(iOS)
- // 发送低内存警告
- UIApplication.shared.perform(Selector(("_performMemoryWarning")))
- #endif
- }
- func detectSimilarPhotos(
- assets: PHFetchResult<PHAsset>,
- progressHandler: @escaping (String, Float) -> Void,
- completion: @escaping ([[PHAsset]]) -> Void
- ) {
- var similarGroups: [[PHAsset]] = []
- let group = DispatchGroup()
-
- if #available(iOS 13.0, *) {
- var imageFeatures: [(asset: PHAsset, feature: VNFeaturePrintObservation)] = []
-
- // 创建处理队列
- let processingQueue = DispatchQueue(label: "com.app.similarPhotos", qos: .userInitiated)
- let semaphore = DispatchSemaphore(value: 4) // 增加并发数以提高效率
-
- // 1. 提取所有图片的特征
- let totalAssets = assets.count
- var processedAssets = 0
-
- progressHandler("正在加载照片...", 0.0)
-
- for i in 0..<assets.count {
- let asset = assets[i]
- group.enter()
- semaphore.wait()
-
- let options = PHImageRequestOptions()
- options.deliveryMode = .fastFormat // 使用快速模式
- options.isSynchronous = false
- options.resizeMode = .fast
-
- DispatchQueue.global(qos: .background).async {
- PHImageManager.default().requestImage(
- for: asset,
- targetSize: CGSize(width: 128, height: 128), // 降低分辨率
- contentMode: .aspectFit,
- options: options
- ) { image, _ in
- defer {
- semaphore.signal()
- }
-
- guard let image = image,
- let cgImage = image.cgImage else {
- group.leave()
- return
- }
-
- processingQueue.async {
- do {
- let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
- let request = VNGenerateImageFeaturePrintRequest()
- try requestHandler.perform([request])
-
- if let result = request.results?.first as? VNFeaturePrintObservation {
- imageFeatures.append((asset, result))
-
- // 更新特征提取进度
- processedAssets += 1
- let progress = Float(processedAssets) / Float(totalAssets)
- progressHandler("正在提取特征...", progress * 0.6)
- }
- } catch {
- print("特征提取失败: \(error)")
- }
- group.leave()
- }
- }
- }
- }
-
- group.notify(queue: processingQueue) {
- progressHandler("正在比较相似度...", 0.6)
-
- // 近似度
- let similarityThreshold: Float = 0.7
- var similarGroups: [[PHAsset]] = []
-
- // 使用并行处理来加速比较
- let processingGroup = DispatchGroup()
- let processingQueue = DispatchQueue(label: "com.yourapp.similarity.processing", attributes: .concurrent)
- let resultsQueue = DispatchQueue(label: "com.yourapp.similarity.results")
- let semaphore = DispatchSemaphore(value: 4) // 减少并发数量
-
- // 创建一个线程安全的数据结构来存储结果
- var processedIndices = Atomic<Set<Int>>(Set<Int>())
- var groupResults = Atomic<[Int: [PHAsset]]>([:])
-
- // 分批处理,每批处理一部分数据
- let batchSize = min(50, imageFeatures.count)
- // 修复 Float 转换错误
- let batchCount = Float(imageFeatures.count) / Float(batchSize)
- let batches = batchCount.isFinite ? Int(ceil(batchCount)) : 1
-
- for batchIndex in 0..<batches {
- let startIndex = batchIndex * batchSize
- let endIndex = min(startIndex + batchSize, imageFeatures.count)
-
- for i in startIndex..<endIndex {
- // 检查是否已处理
- if processedIndices.value.contains(i) { continue }
-
- semaphore.wait()
- processingGroup.enter()
-
- processingQueue.async {
- // 再次检查,因为可能在等待期间被其他线程处理
- if processedIndices.value.contains(i) {
- semaphore.signal()
- processingGroup.leave()
- return
- }
-
- var similarAssets: [PHAsset] = [imageFeatures[i].asset]
- processedIndices.mutate { $0.insert(i) }
-
- for j in (i + 1)..<imageFeatures.count {
- // 检查是否已处理
- if processedIndices.value.contains(j) { continue }
-
- do {
- var distance: Float = 0
- try imageFeatures[i].feature.computeDistance(&distance, to: imageFeatures[j].feature)
-
- // 检查距离值是否有效
- if distance.isNaN || distance.isInfinite {
- print("警告: 检测到无效的距离值")
- continue
- }
-
- // 确保距离在有效范围内
- distance = max(0, min(1, distance))
-
- let similarity = 1 - distance
- if similarity >= similarityThreshold {
- similarAssets.append(imageFeatures[j].asset)
- processedIndices.mutate { $0.insert(j) }
- }
- } catch {
- print("相似度计算失败: \(error)")
- }
- }
-
- // 只保存有多个相似图像的组
- if similarAssets.count > 1 {
- resultsQueue.async {
- groupResults.mutate { $0[i] = similarAssets }
- }
- }
-
- // 更新进度 - 添加安全检查
- if imageFeatures.count > 0 {
- let processedCount = Float(processedIndices.value.count)
- let totalCount = Float(imageFeatures.count)
-
- // 确保进度值有效
- var progress: Float = 0
- if processedCount.isFinite && totalCount.isFinite && totalCount > 0 {
- progress = processedCount / totalCount
- // 限制进度范围
- progress = max(0, min(1, progress))
- }
-
- DispatchQueue.main.async {
- progressHandler("正在比较相似度...", 0.6 + progress * 0.4)
- }
- }
-
- semaphore.signal()
- processingGroup.leave()
- }
- }
- }
-
- processingGroup.wait()
-
- // 整理结果
- similarGroups = Array(groupResults.value.values)
-
- // 按照照片数量降序排序
- similarGroups.sort { $0.count > $1.count }
-
- DispatchQueue.main.async {
- completion(similarGroups)
- }
- }
- }
- }
- func classifyByLocation(assets: PHFetchResult<PHAsset>,
- completion: @escaping ([String: [PHAsset]]) -> Void) {
- var locationGroups: [String: [PHAsset]] = [:]
- let group = DispatchGroup()
- let geocodeQueue = DispatchQueue(label: "com.app.geocoding")
- let semaphore = DispatchSemaphore(value: 10) // 限制并发请求数
- assets.enumerateObjects { asset, _, _ in
- if let location = asset.location {
- group.enter()
- semaphore.wait()
-
- geocodeQueue.async {
- let geocoder = CLGeocoder()
- geocoder.reverseGeocodeLocation(location) { placemarks, error in
- defer {
- semaphore.signal()
- group.leave()
- }
-
- if let placemark = placemarks?.first {
- let locationName = self.formatLocationName(placemark)
- DispatchQueue.main.async {
- if locationGroups[locationName] == nil {
- locationGroups[locationName] = []
- }
- locationGroups[locationName]?.append(asset)
- }
- }
- }
- }
- }
- }
- // 等待所有地理编码完成后回调
- group.notify(queue: .main) {
- completion(locationGroups)
- }
- }
- // 格式化地点名称(只返回城市名)
- func formatLocationName(_ placemark: CLPlacemark) -> String {
- if let city = placemark.locality {
- return city
- } else if let area = placemark.administrativeArea {
- return area
- }
- return "其他"
- }
-
- func classifyByPeople(assets: PHFetchResult<PHAsset>,
- completion: @escaping ([String: [PHAsset]]) -> Void) {
- // 创建结果字典
- var peopleGroups: [String: [PHAsset]] = [:]
- peopleGroups["包含人脸的照片"] = []
-
- // 使用主队列确保安全完成
- let mainCompletion: ([String: [PHAsset]]) -> Void = { result in
- DispatchQueue.main.async {
- completion(result)
- }
- }
-
- // 限制处理的照片数量,防止内存过载
- let totalCount = min(500, assets.count)
- if totalCount == 0 {
- mainCompletion(peopleGroups)
- return
- }
-
- // 创建专用队列
- let processingQueue = DispatchQueue(label: "com.app.peopleDetection", qos: .userInitiated, attributes: .concurrent)
- let resultQueue = DispatchQueue(label: "com.app.peopleResult", qos: .userInitiated)
-
- // 使用NSLock替代原子操作,更安全
- let resultLock = NSLock()
-
- // 创建进度追踪
- let processedCount = Atomic<Int>(0)
-
- // 分批处理,每批处理一部分数据
- let batchSize = 20
- let batches = Int(ceil(Float(totalCount) / Float(batchSize)))
-
- // 创建一个组来等待所有操作完成
- let group = DispatchGroup()
-
- // 创建一个Vision请求处理器
- let faceDetectionRequest = VNDetectFaceRectanglesRequest()
-
- // 防止过早释放
- var strongSelf: AnyObject? = self
-
- for batchIndex in 0..<batches {
- let startIndex = batchIndex * batchSize
- let endIndex = min(startIndex + batchSize, totalCount)
-
- // 每批处理前进入组
- group.enter()
-
- // 使用延迟减轻主线程压力
- DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + Double(batchIndex) * 0.3) { [weak self] in
- guard let self = self else {
- group.leave()
- return
- }
-
- // 创建批次内的处理组
- let batchGroup = DispatchGroup()
- // 限制并发数
- let batchSemaphore = DispatchSemaphore(value: 2)
-
- for i in startIndex..<endIndex {
- batchGroup.enter()
- batchSemaphore.wait()
-
- processingQueue.async {
- // 使用自动释放池减少内存占用
- autoreleasepool {
- let asset = assets[i]
-
- // 降低处理图片的分辨率
- let options = PHImageRequestOptions()
- options.deliveryMode = .fastFormat
- options.isSynchronous = false
- options.resizeMode = .fast
- options.isNetworkAccessAllowed = false
-
- PHImageManager.default().requestImage(
- for: asset,
- targetSize: CGSize(width: 120, height: 120),
- contentMode: .aspectFit,
- options: options
- ) { image, info in
- defer {
- batchSemaphore.signal()
- batchGroup.leave()
- }
-
- // 检查是否是降级的图像
- if let degraded = info?[PHImageResultIsDegradedKey] as? Bool, degraded {
- return
- }
-
- guard let image = image, let cgImage = image.cgImage else {
- return
- }
-
- // 使用简化的人脸检测
- let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
-
- do {
- try handler.perform([faceDetectionRequest])
- if let results = faceDetectionRequest.results, !results.isEmpty {
- // 检测到人脸,添加到数组
- resultLock.lock()
- peopleGroups["包含人脸的照片"]?.append(asset)
- resultLock.unlock()
- }
- } catch {
- print("人脸检测失败: \(error)")
- }
-
- // 更新进度
- processedCount.mutate { $0 += 1 }
- }
- }
- }
- }
-
- // 等待批次内所有处理完成
- batchGroup.wait()
-
- // 每批处理完后清理内存
- self.cleanupMemory()
-
- // 批次完成
- group.leave()
- }
- }
-
- // 设置超时保护
- let timeoutWorkItem = DispatchWorkItem {
- print("人脸检测超时,返回当前结果")
- mainCompletion(peopleGroups)
- strongSelf = nil
- }
-
- // 30秒后超时
- DispatchQueue.global().asyncAfter(deadline: .now() + 30, execute: timeoutWorkItem)
-
- // 等待所有检测完成后更新结果
- group.notify(queue: .main) {
- // 取消超时
- timeoutWorkItem.cancel()
-
- // 最终清理内存
- self.cleanupMemory()
-
- // 返回结果
- mainCompletion(peopleGroups)
-
- // 释放引用
- strongSelf = nil
- }
- }
- // 优化的人脸检测方法
- private func optimizedFaceDetection(in image: UIImage, request: VNDetectFaceRectanglesRequest, completion: @escaping (Bool) -> Void) {
- guard let cgImage = image.cgImage else {
- completion(false)
- return
- }
-
- // 在后台线程执行检测
- DispatchQueue.global(qos: .userInitiated).async {
- autoreleasepool {
- let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
-
- do {
- try handler.perform([request])
- let hasFace = request.results?.isEmpty == false
- completion(hasFace)
- } catch {
- print("人脸检测失败: \(error)")
- completion(false)
- }
- }
- }
- }
-
- // func classifyByPeople(assets: PHFetchResult<PHAsset>,
- // completion: @escaping ([String: [PHAsset]]) -> Void) {
- // var peopleGroups: [String: [PHAsset]] = [:]
- // let group = DispatchGroup()
- //
- // // 创建专用队列和信号量控制并发
- // let processingQueue = DispatchQueue(label: "com.app.peopleDetection", qos: .userInitiated, attributes: .concurrent)
- // let resultQueue = DispatchQueue(label: "com.app.peopleResult", qos: .userInitiated)
- // let semaphore = DispatchSemaphore(value: 4) // 限制并发数
- //
- // // 创建进度追踪
- // var processedCount = 0
- // let totalCount = assets.count
- //
- // // 分批处理,每批处理一部分数据
- // let batchSize = 50
- // let batches = Int(ceil(Float(assets.count) / Float(batchSize)))
- //
- // for batchIndex in 0..<batches {
- // let startIndex = batchIndex * batchSize
- // let endIndex = min(startIndex + batchSize, assets.count)
- //
- // // 使用自动释放池减少内存占用
- // autoreleasepool {
- // for i in startIndex..<endIndex {
- // let asset = assets[i]
- // group.enter()
- // semaphore.wait()
- //
- // // 降低处理图片的分辨率
- // let options = PHImageRequestOptions()
- // options.deliveryMode = .fastFormat
- // options.isSynchronous = false
- // options.resizeMode = .fast
- //
- // processingQueue.async {
- // // 使用自动释放池减少内存占用
- // autoreleasepool {
- // let _ = PHImageManager.default().requestImage(
- // for: asset,
- // targetSize: CGSize(width: 128, height: 128), // 降低分辨率
- // contentMode: .aspectFit,
- // options: options
- // ) { image, _ in
- // defer {
- // semaphore.signal()
- // }
- //
- // guard let image = image else {
- // group.leave()
- // return
- // }
- //
- // // 使用 Vision 框架检测人脸
- // guard let ciImage = CIImage(image: image) else {
- // group.leave()
- // return
- // }
- //
- // let request = VNDetectFaceRectanglesRequest()
- // let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
- //
- // do {
- // try handler.perform([request])
- // if let results = request.results, !results.isEmpty {
- // // 检测到人脸,添加到数组
- // resultQueue.async {
- // if peopleGroups["包含人脸的照片"] == nil {
- // peopleGroups["包含人脸的照片"] = []
- // }
- // peopleGroups["包含人脸的照片"]?.append(asset)
- // }
- // }
- // } catch {
- // print("人脸检测失败: \(error)")
- // }
- //
- // // 更新进度
- // resultQueue.async {
- // processedCount += 1
- // let progress = Float(processedCount) / Float(totalCount)
- // if processedCount % 100 == 0 || processedCount == totalCount {
- // DispatchQueue.main.async {
- // print("人脸检测进度: \(Int(progress * 100))%")
- // }
- // }
- // }
- //
- // group.leave()
- // }
- // }
- // }
- // }
- // }
- //
- // // 每批处理完后清理内存
- // cleanupMemory()
- // }
- //
- // // 等待所有检测完成后更新结果
- // group.notify(queue: .main) {
- // completion(peopleGroups)
- // }
- // }
- // // 添加内存清理方法(如果还没有)
- // private func cleanupMemory() {
- // // 强制清理内存
- // autoreleasepool {
- // // 触发内存警告,促使系统回收内存
- // UIApplication.shared.performSelector(onMainThread: #selector(UIApplication.beginIgnoringInteractionEvents), with: nil, waitUntilDone: true)
- // UIApplication.shared.performSelector(onMainThread: #selector(UIApplication.endIgnoringInteractionEvents), with: nil, waitUntilDone: true)
- // }
- // }
- // 按人物分类
- // func classifyByPeople(assets: PHFetchResult<PHAsset>,
- // completion: @escaping ([String: [PHAsset]]) -> Void) {
- // var peopleGroups: [String: [PHAsset]] = [:]
- // let group = DispatchGroup()
- //
- // DispatchQueue.global(qos: .background).async {
- // // 创建一个数组来存储检测到人脸的照片
- // var facesArray: [PHAsset] = []
- //
- // // 遍历所有照片
- // assets.enumerateObjects { asset, _, _ in
- // group.enter()
- //
- // // 获取照片的缩略图进行人脸检测
- // let options = PHImageRequestOptions()
- // options.isSynchronous = false
- // options.deliveryMode = .fastFormat
- //
- // PHImageManager.default().requestImage(
- // for: asset,
- // targetSize: CGSize(width: 128, height: 128), // 使用较小的尺寸提高性能
- // contentMode: .aspectFit,
- // options: options
- // ) { image, _ in
- // guard let image = image else {
- // group.leave()
- // return
- // }
- //
- // // 使用 Vision 框架检测人脸
- // guard let ciImage = CIImage(image: image) else {
- // group.leave()
- // return
- // }
- //
- // let request = VNDetectFaceRectanglesRequest()
- // let handler = VNImageRequestHandler(ciImage: ciImage)
- //
- // do {
- // try handler.perform([request])
- // if let results = request.results, !results.isEmpty {
- // // 检测到人脸,添加到数组
- // DispatchQueue.main.async {
- // facesArray.append(asset)
- // }
- // }
- // } catch {
- // print("人脸检测失败: \(error)")
- // }
- //
- // group.leave()
- // }
- // }
- //
- // // 等待所有检测完成后更新结果
- // group.notify(queue: .main) {
- // if !facesArray.isEmpty {
- // peopleGroups["包含人脸的照片"] = facesArray
- // }
- // completion(peopleGroups)
- // }
- // }
- // }
- // 识别截图
- func fetchScreenshots(from assets: PHFetchResult<PHAsset>,
- completion: @escaping ([PHAsset]) -> Void) {
- var screenshots: [PHAsset] = []
- // 获取系统的截图智能相册
- let screenshotAlbums = PHAssetCollection.fetchAssetCollections(
- with: .smartAlbum,
- subtype: .smartAlbumScreenshots,
- options: nil
- )
- // 从截图相册中获取所有截图
- screenshotAlbums.enumerateObjects { collection, _, _ in
- let fetchOptions = PHFetchOptions()
- let screenshotAssets = PHAsset.fetchAssets(in: collection, options: fetchOptions)
- screenshotAssets.enumerateObjects { asset, _, _ in
- screenshots.append(asset)
- }
- }
- completion(screenshots)
- }
-
- // 修改辅助方法以接受 PHFetchResult<PHAsset>
- // private func detectScreenshots(assets: PHFetchResult<PHAsset>, completion: @escaping ([PHAsset]) -> Void) {
- // let processingQueue = DispatchQueue(label: "com.yourapp.screenshots.processing", attributes: .concurrent)
- // let resultQueue = DispatchQueue(label: "com.yourapp.screenshots.results")
- // let group = DispatchGroup()
- // let semaphore = DispatchSemaphore(value: 4) // 限制并发数
- //
- // let screenshots = Atomic<[PHAsset]>([])
- //
- // // 分批处理
- // let totalCount = assets.count
- // let batchSize = 50
- // let batches = Int(ceil(Float(totalCount) / Float(batchSize)))
- //
- // for batchIndex in 0..<batches {
- // let startIndex = batchIndex * batchSize
- // let endIndex = min(startIndex + batchSize, totalCount)
- //
- // processingQueue.async {
- // autoreleasepool {
- // for i in startIndex..<endIndex {
- // semaphore.wait()
- // group.enter()
- //
- // let asset = assets.object(at: i)
- //
- // // 检测是否为截图的逻辑
- // // ...
- //
- // // 模拟检测逻辑
- // let isScreenshot = asset.pixelWidth == asset.pixelHeight * 16 / 9 ||
- // asset.pixelHeight == asset.pixelWidth * 16 / 9
- //
- // if isScreenshot {
- // resultQueue.async {
- // screenshots.mutate { $0.append(asset) }
- // }
- // }
- //
- // semaphore.signal()
- // group.leave()
- // }
- // }
- // }
- // }
- //
- // group.notify(queue: .main) {
- // completion(screenshots.value)
- // }
- // }
-
- // ... existing code ...
- func detectBlurryPhotos(from assets: PHFetchResult<PHAsset>, completion: @escaping ([PHAsset]) -> Void) {
- var blurryPhotos: [PHAsset] = []
- let group = DispatchGroup()
- let processingQueue = DispatchQueue(label: "com.app.blurryDetection", attributes: .concurrent)
- let resultQueue = DispatchQueue(label: "com.app.blurryResult")
- let semaphore = DispatchSemaphore(value: 8) // 增加并发数
-
- // 创建进度追踪
- var processedCount = 0
- let totalCount = assets.count
-
- // 分批处理,每批处理一部分数据
- let batchSize = 50
- let batches = Int(ceil(Float(assets.count) / Float(batchSize)))
-
- for batchIndex in 0..<batches {
- let startIndex = batchIndex * batchSize
- let endIndex = min(startIndex + batchSize, assets.count)
-
- autoreleasepool {
- for i in startIndex..<endIndex {
- let asset = assets[i]
- group.enter()
- semaphore.wait()
-
- let options = PHImageRequestOptions()
- options.deliveryMode = .fastFormat // 使用快速模式
- options.isSynchronous = false
- options.resizeMode = .fast
-
- // 进一步降低处理图片的分辨率
- PHImageManager.default().requestImage(
- for: asset,
- targetSize: CGSize(width: 64, height: 64), // 降低分辨率到64x64
- contentMode: .aspectFit,
- options: options
- ) { image, _ in
- defer {
- semaphore.signal()
- }
-
- guard let image = image else {
- group.leave()
- return
- }
-
- processingQueue.async {
- // 使用更高效的模糊检测
- let isBlurry = self.fastBlurCheck(image)
-
- if isBlurry {
- resultQueue.async {
- blurryPhotos.append(asset)
- }
- }
-
- // 更新进度
- resultQueue.async {
- processedCount += 1
- let progress = Float(processedCount) / Float(totalCount)
- if processedCount % 100 == 0 || processedCount == totalCount {
- DispatchQueue.main.async {
- print("模糊检测进度: \(Int(progress * 100))%")
- }
- }
- }
-
- group.leave()
- }
- }
- }
- }
-
- // 每批处理完后清理内存
- cleanupMemory()
- }
-
- group.notify(queue: .main) {
- completion(blurryPhotos)
- }
- }
- // 更高效的模糊检测方法
- private func fastBlurCheck(_ image: UIImage) -> Bool {
- guard let cgImage = image.cgImage else { return false }
-
- // 使用更小的采样区域
- let width = cgImage.width
- let height = cgImage.height
- let pixelStride = 4 // 改名为pixelStride,避免与函数名冲突
-
- // 提前检查图像尺寸是否合法
- guard width > (2 * pixelStride), height > (2 * pixelStride) else {
- return false
- }
-
- // 使用vImage进行快速处理
- var buffer = [UInt8](repeating: 0, count: width * height)
-
- let colorSpace = CGColorSpaceCreateDeviceGray()
- guard let context = CGContext(
- data: &buffer,
- width: width,
- height: height,
- bitsPerComponent: 8,
- bytesPerRow: width,
- space: colorSpace,
- bitmapInfo: CGImageAlphaInfo.none.rawValue
- ) else {
- return false
- }
-
- context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
-
- // 使用拉普拉斯算子的简化版本
- var score: Double = 0
- var sampledPixels = 0
-
- // 只采样图像的一部分区域
- let sampleRows = 10
- let sampleCols = 10
-
- // 计算步长
- let rowStep = max(1, height / sampleRows)
- let colStep = max(1, width / sampleCols)
-
- // 使用Swift的stride函数
- for y in Swift.stride(from: pixelStride, to: height - pixelStride, by: rowStep) {
- for x in Swift.stride(from: pixelStride, to: width - pixelStride, by: colStep) {
- let current = Int(buffer[y * width + x])
- let left = Int(buffer[y * width + (x - pixelStride)])
- let right = Int(buffer[y * width + (x + pixelStride)])
- let top = Int(buffer[(y - pixelStride) * width + x])
- let bottom = Int(buffer[(y + pixelStride) * width + x])
-
- // 简化的边缘检测
- let dx = abs(left - right)
- let dy = abs(top - bottom)
- score += Double(max(dx, dy))
- sampledPixels += 1
- }
- }
-
- // 避免除以零
- guard sampledPixels > 0 else { return false }
-
- // 归一化分数
- let normalizedScore = score / Double(sampledPixels)
-
- // 调整阈值
- let threshold = 15.0
- return normalizedScore < threshold
- }
- // ... existing code ...
-
- // func detectBlurryPhotos(from assets: PHFetchResult<PHAsset>, completion: @escaping ([PHAsset]) -> Void) {
- // var blurryPhotos: [PHAsset] = []
- // let group = DispatchGroup()
- // let processingQueue = DispatchQueue(label: "com.app.blurryDetection", attributes: .concurrent)
- // let resultQueue = DispatchQueue(label: "com.app.blurryResult")
- // let semaphore = DispatchSemaphore(value: 5) // 增加并发数
- //
- // // 创建进度追踪
- // var processedCount = 0
- // let totalCount = assets.count
- //
- // for i in 0..<assets.count {
- // let asset = assets[i]
- // group.enter()
- // semaphore.wait()
- //
- // let options = PHImageRequestOptions()
- // options.deliveryMode = .fastFormat // 使用快速模式
- // options.isSynchronous = false
- // options.resizeMode = .fast
- //
- // // 降低处理图片的分辨率
- // PHImageManager.default().requestImage(
- // for: asset,
- // targetSize: CGSize(width: 128, height: 128), // 降低分辨率
- // contentMode: .aspectFit,
- // options: options
- // ) { image, _ in
- // defer {
- // semaphore.signal()
- // }
- //
- // guard let image = image,
- // let cgImage = image.cgImage else {
- // group.leave()
- // return
- // }
- //
- // processingQueue.async {
- // // 快速模糊检测
- // let isBlurry = self.quickBlurCheck(cgImage)
- //
- // if isBlurry {
- // resultQueue.async {
- // blurryPhotos.append(asset)
- // }
- // }
- //
- // // 更新进度
- // resultQueue.async {
- // processedCount += 1
- // let progress = Float(processedCount) / Float(totalCount)
- // DispatchQueue.main.async {
- // print("模糊检测进度: \(Int(progress * 100))%")
- // }
- // }
- //
- // group.leave()
- // }
- // }
- // }
- //
- // group.notify(queue: .main) {
- // completion(blurryPhotos)
- // }
- // }
- //
- // // 快速模糊检测方法
- // private func quickBlurCheck(_ image: CGImage) -> Bool {
- //
- // let width = image.width
- // let height = image.height
- // let stride = 2 // 跳过一些像素以加快速度
- //
- // // 提前检查图像尺寸是否合法
- // guard width > (2 * stride), height > (2 * stride) else {
- // return false // 小尺寸图像直接判定为模糊或清晰
- // }
- //
- // var buffer = [UInt8](repeating: 0, count: width * height)
- //
- // let colorSpace = CGColorSpaceCreateDeviceGray()
- // guard let context = CGContext(
- // data: &buffer,
- // width: width,
- // height: height,
- // bitsPerComponent: 8,
- // bytesPerRow: width,
- // space: colorSpace,
- // bitmapInfo: CGImageAlphaInfo.none.rawValue
- // ) else {
- // return false
- // }
- //
- // context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
- //
- // // 使用简化的拉普拉斯算子
- // var score: Double = 0
- //
- // for y in stride..<(height-stride) where y % stride == 0 {
- // for x in stride..<(width-stride) where x % stride == 0 {
- // let current = Int(buffer[y * width + x])
- // let left = Int(buffer[y * width + (x - stride)])
- // let right = Int(buffer[y * width + (x + stride)])
- // let top = Int(buffer[(y - stride) * width + x])
- // let bottom = Int(buffer[(y + stride) * width + x])
- //
- // // 简化的边缘检测
- // let dx = abs(left - right)
- // let dy = abs(top - bottom)
- // score += Double(max(dx, dy))
- // }
- // }
- //
- // // 归一化分数
- // let normalizedScore = score / Double((width * height) / (stride * stride))
- //
- // // 调整阈值(可能需要根据实际效果调整)
- // let threshold = 20.0
- // return normalizedScore < threshold
- // }
- }
- extension ClassifyPhoto {
-
- // 获取资源大小的辅助方法
- func getAssetSize(_ asset: PHAsset, completion: @escaping (Int64) -> Void) {
- DispatchQueue.global(qos: .background).async {
- let resources = PHAssetResource.assetResources(for: asset)
- if let resource = resources.first {
- var size: Int64 = 0
- if let unsignedInt64 = resource.value(forKey: "fileSize") as? CLong {
- size = Int64(unsignedInt64)
- }
- DispatchQueue.main.async {
- completion(size)
- }
- } else {
- DispatchQueue.main.async {
- completion(0)
- }
- }
- }
- }
- // 计算资产组的总大小
- func calculateAssetsSize(_ assets: [PHAsset], completion: @escaping (PhotoSizeInfo) -> Void) {
- print("正在计算图片组容量大小")
- let group = DispatchGroup()
- var totalSize: Int64 = 0
-
- for asset in assets {
- group.enter()
- getAssetSize(asset) { size in
- totalSize += size
- group.leave()
- }
- }
-
- group.notify(queue: .main) {
- completion(PhotoSizeInfo(totalSize: totalSize, count: assets.count))
- }
- }
- }
- extension ClassifyPhoto {
-
- // 添加一个处理 P3 色彩空间图像的辅助方法
- private func processImageWithSafeColorSpace(_ image: UIImage) -> UIImage? {
- autoreleasepool {
- guard let cgImage = image.cgImage else { return image }
-
- // 检查色彩空间
- if let colorSpace = cgImage.colorSpace,
- (colorSpace.name as String?) == CGColorSpace.displayP3 as String {
-
- // 转换为 sRGB 色彩空间
- let sRGBColorSpace = CGColorSpaceCreateDeviceRGB()
- if let context = CGContext(
- data: nil,
- width: cgImage.width,
- height: cgImage.height,
- bitsPerComponent: 8,
- bytesPerRow: 0,
- space: sRGBColorSpace,
- bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
- ) {
- context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height))
- if let convertedImage = context.makeImage() {
- return UIImage(cgImage: convertedImage, scale: image.scale, orientation: image.imageOrientation)
- }
- }
- }
-
- return image
- }
- }
- // 修改图像请求方法,添加色彩空间处理
- private func requestImageWithSafeProcessing(
- for asset: PHAsset,
- targetSize: CGSize,
- contentMode: PHImageContentMode,
- options: PHImageRequestOptions?,
- completion: @escaping (UIImage?) -> Void
- ) {
- PHImageManager.default().requestImage(
- for: asset,
- targetSize: targetSize,
- contentMode: contentMode,
- options: options
- ) { image, info in
- guard let image = image else {
- completion(nil)
- return
- }
-
- // 处理可能的 P3 色彩空间图像
- DispatchQueue.global(qos: .userInitiated).async {
- let processedImage = self.processImageWithSafeColorSpace(image)
- DispatchQueue.main.async {
- completion(processedImage)
- }
- }
- }
- }
- }
- class Atomic<T> {
- private var value_: T
- private let lock = NSLock()
-
- init(_ value: T) {
- self.value_ = value
- }
-
- var value: T {
- lock.lock()
- defer { lock.unlock() }
- return value_
- }
-
- func mutate(_ mutation: (inout T) -> Void) {
- lock.lock()
- defer { lock.unlock() }
- mutation(&value_)
- }
- }
|