大家好,本人是iOS前端,现在有一个开发需求已经卡很久了没有解决, 求助一下大家。 主要功能是查找出手机相册中的所有相似图片和重复图片。 我目前用了vision框架来获取图片特征值, 然后相册图片是按每周一组, 周内图片之间做对比。 特征值用KDTree算法做分类。 现在一个是有性能问题 开始遍历图片后 UI会卡50秒左右, 之后正常。 还有就是因为多线程写数组, 需要互相等待, 效率很低。 大家有更好的思路吗? 愿意有偿

Via (tg://user?id=5812772540)

关注


27人评论了“大家好,本人是iOS前端,现在有一个开发需求已经卡很久了没有解决, 求助一下大家。 主要功能是查找出”

  1. 以下是一些可能的改进思路:

    一、性能问题优化

    1. 延迟加载与异步处理

    • 不要一次性遍历所有图片,可以采用分页加载或者延迟加载的方式。例如,先加载本周的一部分图片进行处理,当用户需要查看更多时再继续加载下一批。

    • 对于图片特征值的提取和对比操作,尽可能放在后台线程中异步执行,避免阻塞主线程导致 UI 卡顿。可以使用 GCD(Grand Central Dispatch)或者 OperationQueue 来管理后台任务。

    2. 缓存机制

    • 考虑对已经处理过的图片特征值进行缓存。如果本周内的图片在后续可能会被多次比较,可以将其特征值缓存起来,下次使用时直接从缓存中读取,避免重复计算。

    • 可以使用数据库或者内存缓存来存储特征值,根据实际情况选择合适的存储方式。

    3. 优化算法

    • 检查 KDTree 算法的实现是否可以进一步优化。可以参考一些优化过的开源实现,或者尝试其他更高效的相似性检测算法。

    • 对于特征值的计算,可以考虑使用更高效的算法或者优化现有的算法实现,减少计算时间。

    二、多线程问题优化

    1. 线程同步机制

    • 使用合适的线程同步机制来避免多线程写数组时的互相等待。可以使用锁(如 NSLock、pthread_mutex)、信号量(dispatch_semaphore)或者原子操作(如 @synchronized、OSAtomic 系列函数)来确保线程安全。

    • 合理设计数据结构和访问方式,尽量减少多线程竞争的情况。例如,可以使用并发数据结构(如 NSMutableArray 的线程安全版本)或者将数据分割到不同的数组中,每个线程处理一部分数据。

    2. 任务队列与依赖关系

    • 使用任务队列来管理多线程任务。将图片处理任务分解为多个小任务,放入任务队列中,由系统自动调度执行。可以根据任务之间的依赖关系设置任务的执行顺序,避免不必要的等待。

    • 例如,可以先将图片加载任务放入队列,然后在图片加载完成后,再将特征值提取任务放入队列,最后进行相似性比较任务。

    3. 性能监控与调试

    • 使用 Instruments 等工具来监控应用的性能,找出性能瓶颈所在。可以分析 CPU 使用率、内存占用、线程状态等信息,针对性地进行优化。

    • 在开发过程中,使用日志记录和调试工具来跟踪多线程执行情况,及时发现和解决问题。

    希望这些思路对你有所帮助。如果问题仍然存在,可以提供更多具体的代码和问题细节,以便更好地分析和解决问题。

  2. 要在用户的照片库中查找相似和重复的图片是一个计算密集型的任务,特别是当处理大量图片时。以下是一些策略,可能有助于解决性能问题并改进你的实现:

    1. **将处理任务放在后台线程:**

    – **避免阻塞主线程:** 确保所有繁重的计算(如特征提取和图片比较)都在主线程之外执行。使用Grand Central Dispatch(GCD)或`NSOperationQueue`来管理异步任务。
    – **使用适当的QoS等级:** 为后台任务分配较低的服务质量(QoS)等级,防止其与UI更新竞争。例如,使用`.utility`或`.background`的QoS等级。

    “`
    DispatchQueue.global(qos: .utility).async {
    // 在这里执行繁重的计算
    DispatchQueue.main.async {
    // 如有必要,更新UI
    }
    }
    “`

    2. **优化特征提取:**

    – **批量处理:** 与其一张一张地处理图片,不如批量处理,减少开销。
    – **高效的特征描述符:** 使用计算需求较低的特征描述符。如果对精度要求不高,可以考虑使用更简单的算法。
    – **重用缓存数据:** 如果可能,缓存特征向量,这样每次应用运行时就不需要重新计算。

    3. **改进数据结构和算法:**

    – **使用并发数据结构:** 用线程安全的并发数据结构替代数组,例如并发队列或内部处理同步的字典。
    – **减少锁竞争:** 最小化使用锁或需要线程等待的同步原语。考虑无锁编程技术,或使用`Dispatch Barriers`等并发读/写机制。

    “`
    let concurrentQueue = DispatchQueue(label: “com.example.app.concurrentQueue”, attributes: .concurrent)
    “`

    4. **实现近似最近邻搜索:**

    – **局部敏感哈希(LSH):** 使用LSH来近似最近邻,大幅减少计算时间,代价是一定的精度损失。
    – **使用高效的库:** 考虑使用针对大规模最近邻搜索优化的第三方库,如FLANN(快速近似最近邻库)。

    5. **优化图片分组策略:**

    – **避免严格的基于时间的分组:** 严格按照每周分组可能并不高效。可以考虑基于特征向量对图片进行分组,而不考虑拍摄时间。
    – **层次聚类:** 使用层次聚类方法更有效地分组相似图片。

    6. **增量式处理:**

    – **分块处理:** 将照片库分成较小的块,分别处理。这种方法可以防止长时间的不响应。
    – **渐进式加载:** 在处理块时向用户显示中间结果,提升性能感知。

    7. **利用机器学习框架:**

    – **Core ML:** 利用针对设备上性能优化的Core ML模型来提取特征或分类图片。
    – **Vision框架的增强功能:** 确保你使用了Vision框架中最新的功能和优化。

    8. **优化多线程:**

    – **线程池管理:** 控制并发线程的数量,防止过多的上下文切换和资源竞争。
    – **线程本地存储:** 将中间结果存储在线程本地存储中,处理完成后再合并,避免共享可变状态。

    9. **用户体验考虑:**

    – **提供反馈:** 显示进度指示器或允许用户取消操作,以改善用户体验。
    – **推迟繁重处理:** 在应用处于后台或用户交互较少时执行密集型任务。

    10. **性能分析和监控:**

    – **Instruments工具:** 使用Xcode的Instruments工具对应用进行分析,找出CPU使用率、内存消耗和线程活动的瓶颈。
    – **优化关键部分:** 重点优化消耗最多资源的代码部分。

    11. **内存管理:**

    – **高效的图片处理:** 在不需要高分辨率时,使用缩小版本的图片进行特征提取。
    – **自动释放池:** 在循环中使用自动释放池,有效管理内存。

    “`
    for image in images {
    autoreleasepool {
    // 处理图片
    }
    }
    “`

    12. **考虑数据库解决方案:**

    – **索引特征:** 将特征向量存储在具有索引功能的数据库中,加速搜索操作。
    – **Core Data:** 使用Core Data并进行适当的索引,以高效管理和查询大型数据集。

    13. **替代方法:**

    – **预构建的解决方案:** 探索现有的专门用于图片相似度检测的解决方案或API。
    – **云端处理:** 如果可行,将繁重的计算卸载到后端服务器,减少设备负载。

    **示例实现概述:**

    “`
    func findSimilarImages() {
    let processingQueue = DispatchQueue(label: “com.example.app.processingQueue”, attributes: .concurrent)
    let group = DispatchGroup()
    let concurrentResults = DispatchQueue(label: “com.example.app.resultsQueue”, attributes: .concurrent)
    var featureVectors = () // 使用线程安全的字典,或进行同步处理

    for image in images {
    group.enter()
    processingQueue.async {
    let featureVector = extractFeatures(from: image)
    concurrentResults.async(flags: .barrier) {
    featureVectors = featureVector
    group.leave()
    }
    }
    }

    group.notify(queue: .main) {
    // 继续进行KDTree分类或更新UI
    }
    }
    “`

    **关键要点:**

    – **异步处理:** 始终异步执行繁重任务,保持UI响应。
    – **线程安全:** 在访问共享资源时,使用线程安全的数据结构或同步机制。
    – **性能优化:** 对应用进行分析,找到并优化性能瓶颈。
    – **算法效率:** 根据具体用例,选择在性能和准确性之间提供最佳权衡的算法和数据结构。

    通过实施这些策略,你应该能够显著减少UI冻结时间,并提高应用的整体效率。

    答案来自 o1-preview

  3. 白泽(飞机不涉及财务)

    针对您提到的iOS前端开发需求,即查找手机相册中的所有相似图片和重复图片,确实存在一些性能优化的方向。这里提供几个可能的解决方案和建议:

    ### 1. 异步加载与处理图片
    确保所有耗时操作都在后台线程执行,避免阻塞主线程导致UI卡顿。可以使用`GCD`(Grand Central Dispatch)或者`OperationQueue`来管理任务队列,确保图片加载和处理不会干扰用户界面的响应性。

    ### 2. 图片预处理
    在开始分析之前,可以先将图片压缩成较小的尺寸再进行特征提取。这样不仅可以加快处理速度,还能减少内存占用。Vision框架支持多种输入尺寸,选择合适的尺寸平衡精度和性能。

    ### 3. 使用更高效的索引结构
    虽然KD树是一个不错的选择,但在高维空间中其效率会下降。您可以考虑使用FLANN (Fast Library for Approximate Nearest Neighbors) 或者HNSW (Hierarchical Navigable Small World graphs),它们在处理大规模数据集时表现更好。

    ### 4. 并行化处理
    为了提高多线程处理的效率,可以采用生产者-消费者模式。创建一个线程池来处理图片,每个线程负责一部分图片的特征提取和比较工作。使用`DispatchGroup`来同步多个异步任务,确保所有任务完成后才继续下一步操作。

    ### 5. 缓存机制
    对于已经处理过的图片,可以将其特征值缓存起来,下次直接读取缓存结果而不是重新计算。这可以通过本地数据库(如Core Data或SQLite)实现,或者简单地将数据存储在文件系统中。

    ### 6. 逐步加载
    如果一次性加载全部图片会导致性能问题,可以尝试分批加载图片。例如,每次只加载并处理一周内的图片,完成后再加载下一批。这样可以有效降低内存峰值,提高应用稳定性。

    ### 7. 用户反馈
    在执行长时间任务时,为用户提供明确的进度提示和取消选项,提升用户体验。可以显示一个进度条或旋转加载动画,让用户知道程序正在工作。

    ### 8. 测试与优化
    最后,确保对不同设备和iOS版本进行充分测试,识别瓶颈所在,并针对性地进行优化。利用Xcode的Instruments工具可以帮助您分析应用性能,找到改进的空间。

    除了您已经在使用的KD树算法外,还有几种高效且适合处理大规模图像数据集的相似性和重复性检测算法。下面是一些推荐的替代方案:

    ### 1. 局部敏感哈希(LSH, Locality Sensitive Hashing)
    LSH 是一种近似最近邻搜索技术,特别适用于高维数据。它通过设计特殊的哈希函数,使得相似的数据点具有更高的概率被映射到相同的桶中。这样,在查找相似项时,只需在同一个桶内的元素之间进行比较,大大减少了计算量。

    ### 2. HNSW(Hierarchical Navigable Small World Graphs)
    HNSW 是一种非常高效的最近邻搜索算法,尤其适用于大规模数据集。它构建了一个层次化的导航图结构,可以在保持较低查询时间的同时,获得较高的召回率。HNSW 在实际应用中表现出色,尤其是在处理高维数据时。

    ### 3. Annoy(Approximate Nearest Neighbors Oh Yeah)
    Annoy 是由 Spotify 开发的一个开源库,用于快速搜索近似最近邻。它通过构建一棵森林结构,每棵树都是一个随机投影树,从而实现高效搜索。Annoy 支持多种距离度量方式,包括欧氏距离、余弦相似度等。

    ### 4. Faiss(Facebook AI Similarity Search)
    Faiss 是 Facebook 开源的一个高效相似性搜索库,特别适合大规模向量搜索。它支持多种索引类型,包括基于量化的方法(如 PQ、IVF),可以显著提高搜索速度。Faiss 还提供了 GPU 加速版本,进一步提升了性能。

    ### 5. FLANN(Fast Library for Approximate Nearest Neighbors)
    FLANN 是一个用于快速最近邻搜索的库,支持多种算法(如 KD 树、K-Means Tree 等)。它可以根据数据集的特点自动选择最优算法,非常适合处理大规模数据集。

    ### 实现步骤
    1. **特征提取**:使用 Vision 框架或其他方法提取图像的特征向量。
    2. **构建索引**:选择合适的算法(如 LSH、HNSW、Annoy 或 Faiss),将特征向量构建索引。
    3. **查询相似图片**:使用索引结构进行快速搜索,找到相似或重复的图片。

    ### 示例代码
    以下是一个使用 Annoy 库的 Python 示例,展示如何构建索引并进行相似图片搜索:

    “`import os
    from PIL import Image
    import numpy as np
    from annoy import AnnoyIndex
    from torchvision import transforms

    # 定义图像预处理
    transform = transforms.Compose(
    transforms.Resize((128, 128)),
    transforms.ToTensor(),
    ])

    # 提取特征向量的函数(示例使用预训练的模型)
    def extract_features(image_path):
    image = Image.open(image_path).convert(‘RGB’)
    image = transform(image)
    # 这里假设你有一个预训练的模型 model
    # features = model(image.unsqueeze(0)).detach().numpy().flatten()
    # 返回一个随机向量作为示例
    return np.random.rand(128)

    # 构建 Annoy 索引
    f = 128 # 特征向量的维度
    t = AnnoyIndex(f, ‘euclidean’)

    # 遍历相册中的图片
    image_paths =
    for i, path in enumerate(image_paths):
    features = extract_features(path)
    t.add_item(i, features)

    # 构建索引
    t.build(10) # 10 表示树的数量

    # 查询相似图片
    query_index = 0 # 查询第 0 张图片
    n_results = 5 # 返回最相似的 5 张图片
    similar_indices = t.get_nns_by_item(query_index, n_results)

    # 输出相似图片的路径
    for index in similar_indices:
    print(image_paths)“`
    ### 总结
    选择合适的算法和库可以显著提高相似图片和重复图片检测的效率。根据您的具体需求和数据集特点,可以尝试上述几种方法

  4. 感谢大家 上面的调优大差不差都试过了, 但效果一般‍ 可能还是我写法问题吧 在我自己手机一万多张图片的情况下 速度还是不行

  5. 个人认为在手机端做计算很困难,碰到老旧手机只会速度更慢,我做过服务器的图像检索,5万个图像,用模型提取特征转为768维向量,写到faiss数据库里,新数据再转特征查库,一条查询耗时大概7ms,5万图像转成向量写入数据库大概耗时94s。

  6. 目前PM给的要求就是手机端完成。 我看了几个类似的清理工具应该也都是在端侧处理的。 速度也都还不错。 但是能找到资料太少了, 自己的思路感觉也有局限性‍ 而且把iPhone相册图片给后端也不现实…

  7. 了解,用户的图片可能就几千张,转换特征应该不会很久,可以先把图片压缩,再做计算。

  8. 已经压缩过了 通过相册PHAsset 请求image的时候, 就已经限定了size 240*240。 感觉其实取图片特征和KD树算法 分类特征都很快, 但就是因为要保证多线程下数组安全 所以必须等待很耗时。 也找不出为啥UI就前几十秒会卡。 看着代码都不知道改哪里了

  9. 而且也没有所有图片之间进行对比。 是先把所有图片按周分组了, 只对周数组内的图片进行对比分类, 计算量应该不算大的

  10. 多线程为什么会等待呢?我理解多线程是在加载图像上(压缩和转特征等),图像加载完成后可以把结果直接塞到容器内(队列、字典、集合什么的),需要考虑什么安全呢?

  11. 我是写了一个PhotoManager单例, 在子线程上分类出相似图片之后, 会将相似图片数组添加到单例的similarImages: ]]三维数组中, 用于更新UI显示。但是子线程同时写入这个数组的话, 会崩溃, 所有就像上面☝️gpt给的答案一样 用了线程安全的.barrier 来写入

  12. 哦哦,UI这块我确实不懂,那现在这种方式是写到三维数组一张图片,UI就会更新,还是要等所有图片都写入完毕,UI才会更新?

  13. 实时更新UI, 比如已找到的相似图片 和count总数之类的。 大部分清理工具应该也是这个逻辑

  14. 好的,了解一点了,可以试试加个队列?多线程向队列写,队列再向三维数组里传数据,这样计算过程就不是阻塞的了。

  15. 一个方向是,先抓取最新的一天或小时先处理,数据返回再继续抓取,不要一次抓取

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部