PhotoClassifier.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. //import Photos
  2. //import Vision
  3. //
  4. //class PhotoClassifier {
  5. // struct ClassifiedPhotos {
  6. // var screenshots: [PHAsset] = []
  7. // var locations: [String: [PHAsset]] = [:] // 按地点分组
  8. // var people: [String: [PHAsset]] = [:] // 按人物分组
  9. // var similarPhotos: [[PHAsset]] = [] // 存储相似照片组
  10. // }
  11. //
  12. // func classifyPhotos(
  13. // assets: PHFetchResult<PHAsset>,
  14. // progressHandler: @escaping (String, Float) -> Void,
  15. // completion: @escaping (ClassifiedPhotos) -> Void
  16. // ) {
  17. // // 在后台队列处理
  18. // DispatchQueue.global(qos: .userInitiated).async {
  19. // var result = ClassifiedPhotos()
  20. // let group = DispatchGroup()
  21. //
  22. // // 开始处理
  23. // DispatchQueue.main.async {
  24. // progressHandler("正在加载照片...", 0.0)
  25. // }
  26. //
  27. // // 1. 检测截图 (占总进度的 20%)
  28. // group.enter()
  29. // self.fetchScreenshots(from: assets) { screenshots in
  30. // result.screenshots = screenshots
  31. // DispatchQueue.main.async {
  32. // progressHandler("正在检测截图...", 0.2)
  33. // }
  34. // group.leave()
  35. // }
  36. //
  37. // // 2. 检测相似照片 (占总进度的 80%)
  38. // group.enter()
  39. // self.detectSimilarPhotos(
  40. // assets: assets,
  41. // progressHandler: { stage, progress in
  42. // // 将相似照片检测的进度映射到 20%-100% 的范围
  43. // let mappedProgress = 0.2 + (progress * 0.8)
  44. // DispatchQueue.main.async {
  45. // progressHandler(stage, mappedProgress)
  46. // }
  47. // }
  48. // ) { similarPhotos in
  49. // result.similarPhotos = similarPhotos
  50. // group.leave()
  51. // }
  52. //
  53. // // 等待所有处理完成
  54. // group.notify(queue: .main) {
  55. // progressHandler("分类完成", 1.0)
  56. // completion(result)
  57. // }
  58. // }
  59. // }
  60. //
  61. // private func detectSimilarPhotos(
  62. // assets: PHFetchResult<PHAsset>,
  63. // progressHandler: @escaping (String, Float) -> Void,
  64. // completion: @escaping ([[PHAsset]]) -> Void
  65. // ) {
  66. // var similarGroups: [[PHAsset]] = []
  67. // let group = DispatchGroup()
  68. // var imageFeatures: [(asset: PHAsset, feature: VNFeaturePrintObservation)] = []
  69. //
  70. // // 创建处理队列
  71. // let processingQueue = DispatchQueue(label: "com.app.similarPhotos", qos: .userInitiated)
  72. // let semaphore = DispatchSemaphore(value: 5)
  73. //
  74. // // 1. 提取所有图片的特征
  75. // let totalAssets = assets.count
  76. // var processedAssets = 0
  77. //
  78. // progressHandler("正在加载照片...", 0.0)
  79. //
  80. // for i in 0..<assets.count {
  81. // let asset = assets[i]
  82. // group.enter()
  83. // semaphore.wait()
  84. //
  85. // let options = PHImageRequestOptions()
  86. // options.deliveryMode = .highQualityFormat
  87. // options.isSynchronous = false
  88. // options.resizeMode = .exact
  89. //
  90. // PHImageManager.default().requestImage(
  91. // for: asset,
  92. // targetSize: CGSize(width: 448, height: 448),
  93. // contentMode: .aspectFit,
  94. // options: options
  95. // ) { image, _ in
  96. // defer {
  97. // semaphore.signal()
  98. // }
  99. //
  100. // guard let image = image,
  101. // let cgImage = image.cgImage else {
  102. // group.leave()
  103. // return
  104. // }
  105. //
  106. // processingQueue.async {
  107. // do {
  108. // let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
  109. // let request = VNGenerateImageFeaturePrintRequest()
  110. // try requestHandler.perform([request])
  111. //
  112. // if let result = request.results?.first as? VNFeaturePrintObservation {
  113. // imageFeatures.append((asset, result))
  114. //
  115. // // 更新特征提取进度
  116. // processedAssets += 1
  117. // let progress = Float(processedAssets) / Float(totalAssets)
  118. // progressHandler("正在提取特征...", progress * 0.6)
  119. // }
  120. // } catch {
  121. // print("特征提取失败: \(error)")
  122. // }
  123. // group.leave()
  124. // }
  125. // }
  126. // }
  127. //
  128. // // 2. 比较特征相似度并分组
  129. // group.notify(queue: processingQueue) {
  130. // progressHandler("正在比较相似度...", 0.6)
  131. //
  132. // // 近似度
  133. // let similarityThreshold: Float = 0.7
  134. // var processedComparisons = 0
  135. // let totalComparisons = (imageFeatures.count * (imageFeatures.count - 1)) / 2
  136. // var processedIndices = Set<Int>()
  137. //
  138. // for i in 0..<imageFeatures.count {
  139. // if processedIndices.contains(i) { continue }
  140. //
  141. // var similarGroup: [PHAsset] = [imageFeatures[i].asset]
  142. // processedIndices.insert(i)
  143. //
  144. // for j in (i + 1)..<imageFeatures.count {
  145. // if processedIndices.contains(j) { continue }
  146. //
  147. // do {
  148. // var distance: Float = 0
  149. // try imageFeatures[i].feature.computeDistance(&distance, to: imageFeatures[j].feature)
  150. //
  151. // let similarity = 1 - distance
  152. // if similarity >= similarityThreshold {
  153. // similarGroup.append(imageFeatures[j].asset)
  154. // processedIndices.insert(j)
  155. // }
  156. //
  157. // // 更新比较进度
  158. // processedComparisons += 1
  159. // let compareProgress = Float(processedComparisons) / Float(totalComparisons)
  160. // progressHandler("正在比较相似度...", 0.6 + compareProgress * 0.4)
  161. // } catch {
  162. // print("相似度计算失败: \(error)")
  163. // }
  164. // }
  165. //
  166. // if similarGroup.count > 1 {
  167. // similarGroups.append(similarGroup)
  168. // }
  169. // }
  170. //
  171. // // 按照照片数量降序排序
  172. // similarGroups.sort { $0.count > $1.count }
  173. //
  174. // DispatchQueue.main.async {
  175. // completion(similarGroups)
  176. // }
  177. // }
  178. // }
  179. //
  180. // // 按地点分类
  181. // private func classifyByLocation(assets: PHFetchResult<PHAsset>,
  182. // completion: @escaping ([String: [PHAsset]]) -> Void) {
  183. // var locationGroups: [String: [PHAsset]] = [:]
  184. // let group = DispatchGroup()
  185. // let geocodeQueue = DispatchQueue(label: "com.app.geocoding")
  186. // let semaphore = DispatchSemaphore(value: 10) // 限制并发请求数
  187. //
  188. // assets.enumerateObjects { asset, _, _ in
  189. // if let location = asset.location {
  190. // group.enter()
  191. // semaphore.wait()
  192. //
  193. // geocodeQueue.async {
  194. // let geocoder = CLGeocoder()
  195. // geocoder.reverseGeocodeLocation(location) { placemarks, error in
  196. // defer {
  197. // semaphore.signal()
  198. // group.leave()
  199. // }
  200. //
  201. // if let placemark = placemarks?.first {
  202. // let locationName = self.formatLocationName(placemark)
  203. // DispatchQueue.main.async {
  204. // if locationGroups[locationName] == nil {
  205. // locationGroups[locationName] = []
  206. // }
  207. // locationGroups[locationName]?.append(asset)
  208. // }
  209. // }
  210. // }
  211. // }
  212. // }
  213. // }
  214. //
  215. // // 等待所有地理编码完成后回调
  216. // group.notify(queue: .main) {
  217. // completion(locationGroups)
  218. // }
  219. // }
  220. //
  221. // // 格式化地点名称(只返回城市名)
  222. // private func formatLocationName(_ placemark: CLPlacemark) -> String {
  223. // if let city = placemark.locality {
  224. // return city
  225. // }
  226. // return "其他"
  227. // }
  228. //
  229. // // 按人物分类
  230. // private func classifyByPeople(assets: PHFetchResult<PHAsset>,
  231. // completion: @escaping ([String: [PHAsset]]) -> Void) {
  232. // var peopleGroups: [String: [PHAsset]] = [:]
  233. // let group = DispatchGroup()
  234. //
  235. // // 创建一个数组来存储检测到人脸的照片
  236. // var facesArray: [PHAsset] = []
  237. //
  238. // // 遍历所有照片
  239. // assets.enumerateObjects { asset, _, _ in
  240. // group.enter()
  241. //
  242. // // 获取照片的缩略图进行人脸检测
  243. // let options = PHImageRequestOptions()
  244. // options.isSynchronous = false
  245. // options.deliveryMode = .fastFormat
  246. //
  247. // PHImageManager.default().requestImage(
  248. // for: asset,
  249. // targetSize: CGSize(width: 500, height: 500), // 使用较小的尺寸提高性能
  250. // contentMode: .aspectFit,
  251. // options: options
  252. // ) { image, _ in
  253. // guard let image = image else {
  254. // group.leave()
  255. // return
  256. // }
  257. //
  258. // // 使用 Vision 框架检测人脸
  259. // guard let ciImage = CIImage(image: image) else {
  260. // group.leave()
  261. // return
  262. // }
  263. //
  264. // let request = VNDetectFaceRectanglesRequest()
  265. // let handler = VNImageRequestHandler(ciImage: ciImage)
  266. //
  267. // do {
  268. // try handler.perform([request])
  269. // if let results = request.results, !results.isEmpty {
  270. // // 检测到人脸,添加到数组
  271. // DispatchQueue.main.async {
  272. // facesArray.append(asset)
  273. // }
  274. // }
  275. // } catch {
  276. // print("人脸检测失败: \(error)")
  277. // }
  278. //
  279. // group.leave()
  280. // }
  281. // }
  282. //
  283. // // 等待所有检测完成后更新结果
  284. // group.notify(queue: .main) {
  285. // if !facesArray.isEmpty {
  286. // peopleGroups["包含人脸的照片"] = facesArray
  287. // }
  288. // completion(peopleGroups)
  289. // }
  290. // }
  291. //
  292. // // 识别截图
  293. // private func fetchScreenshots(from assets: PHFetchResult<PHAsset>,
  294. // completion: @escaping ([PHAsset]) -> Void) {
  295. // var screenshots: [PHAsset] = []
  296. //
  297. // // 获取系统的截图智能相册
  298. // let screenshotAlbums = PHAssetCollection.fetchAssetCollections(
  299. // with: .smartAlbum,
  300. // subtype: .smartAlbumScreenshots,
  301. // options: nil
  302. // )
  303. //
  304. // // 从截图相册中获取所有截图
  305. // screenshotAlbums.enumerateObjects { collection, _, _ in
  306. // let fetchOptions = PHFetchOptions()
  307. // let screenshotAssets = PHAsset.fetchAssets(in: collection, options: fetchOptions)
  308. //
  309. // screenshotAssets.enumerateObjects { asset, _, _ in
  310. // screenshots.append(asset)
  311. // }
  312. // }
  313. //
  314. // completion(screenshots)
  315. // }
  316. //}