如何使用可用内存有效地比较 1,000 张图像

2024-04-24

这是一个棘手的问题。我的磁盘中存储了大约 1,000 张图像,我想通过成对比较来找到彼此相似的图像。所以我必须做周围1,000 * 999 / 2 https://stackoverflow.com/questions/46958633/generate-unique-combinations-of-integers= 499,500 次比较(“相似”属性不具有传递性)。我的问题与如何比较图像无关,而是与如何在比较过程中有效管理机器的内存有关。我已经实现了比较功能:

static bool AreSimilar(ImageInfo x, ImageInfo y)
{
    // Logic
}

...在哪里ImageInfo是一个保存一张图像信息的类:

class ImageInfo : IDisposable
{
    public string Path { get; init; }
    public System.Drawing.Image Image { get; init; }
    public void Dispose() => Image.Dispose();
}

理想情况下,我想将所有 1,000 个图像加载到内存中,然后执行嵌套循环并调用AreSimilar方法,但一次加载所有它们所需的内存远远超出了我的机器的可用内存。图像文件非常大,并且大小差异很大(大多数大小在 5 到 50 MB 之间)。可用 RAM 为 2 GB,因此我不能同时加载超过 80 个图像。从磁盘加载图像非常慢。实际上,从磁盘加载两个图像比比较它们要慢得多 并找出它们是否相似。

我的问题是如何实现一种方法,该方法负责从磁盘加载/卸载图像,并成对生成它们,同时利用所有可用内存,但不超过内存限制。这是我尝试实现的方法的签名:

static IEnumerable<(TItem, TItem)> GetPairs<TSource, TItem>(
    IReadOnlyList<TSource> source,
    Func<TSource, long> sizeSelector,
    Func<TSource, TItem> itemLoader,
    long maxConcurrentSize) where TItem : IDisposable;

The TSource将是文件的路径,并且TItem将是一个ImageInfo。我打算这样使用它:

string[] paths = Directory.GetFiles(@"C:\Images", "*.jpg");
var pairs = GetPairs(paths,
    path => new FileInfo(path).Length,
    path => new ImageInfo() { Path = path, Image = Image.FromFile(path) },
    2_000_000_000);
foreach (var (x, y) in pairs)
{
    if (AreSimilar(x, y))
        Console.WriteLine($"{x.Path} and {y.Path} are similar!");
}

我目前不知道如何实现这个方法。这看起来是一项严肃的任务。我现在拥有的只是下面的简单版本,它成对加载图像并忽略sizeSelector and maxConcurrentSize参数:

static IEnumerable<(TItem, TItem)> GetPairs<TSource, TItem>(
    IReadOnlyList<TSource> source,
    Func<TSource, long> sizeSelector,
    Func<TSource, TItem> itemLoader,
    long maxConcurrentSize) where TItem : IDisposable
{
    for (int i = 0; i < source.Count; i++)
    {
        using var first = itemLoader(source[i]);
        for (int j = i + 1; j < source.Count; j++)
        {
            using var second = itemLoader(source[j]);
            yield return (first, second);
        }
    }
}

显然,性能很糟糕,因为每个图像平均加载约 500 次。


这是一个适合您的问题的解决方案,其中包含单元测试。不幸的是,在一次只能加载少量图像的情况下,它的性能很差,导致最坏的情况是加载数量是您建议的解决方案的 2 倍。然而,当一次可以加载大量图像时,该算法开始优于您的算法,随着允许的内存大小的增加,每个图像的加载次数限制为 1 次。

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTest;


[TestClass]
public class TestComparison
{
    [TestMethod]
    public void Test()
    {
        const int imageCount = 10;
        var (totalLoads, max, pairs) = RunTest(1, 5, 1000, imageCount);
        Assert.AreEqual(10, totalLoads);
        Assert.AreEqual(1, max);

        (_, max, var pairs2) = RunTest(5, 5, 12, imageCount);
        Assert.AreEqual(9, max);

        var expectedPairs = (imageCount - 1) * imageCount / 2;
        Assert.AreEqual(expectedPairs, pairs.Count);
        Assert.AreEqual(expectedPairs, pairs2.Count);
    }

    private (int totalLoads, int maxLoads, List<(ImageInfo, ImageInfo)> pairs) RunTest(int minImageSize, int maxImageSize, long memorySize, int imageCount)
    {
        var images = GenerateImages(imageCount, minImageSize, maxImageSize);
        var paths = images.Keys.ToList();
        var imageLoadCounts = new Dictionary<string, int>();
        var pairs = GetPairs(paths,p=>images[p].Size,LoadImage,memorySize).ToList();

        var totalLoads = imageLoadCounts.Values.Sum();
        var maxLoad = imageLoadCounts.Values.Max();
        return new(totalLoads, maxLoad,pairs);

        ImageInfo LoadImage(string path)
        {
            if (!imageLoadCounts.TryGetValue(path, out int count))
            {
                count = 0;
            }

            count++;
            imageLoadCounts[path] = count;

            return images[path];
        }
    }

    private Dictionary<string, ImageInfo> GenerateImages(int imageCount, int minImageSize, int maxImageSize)
    {
        var images = new Dictionary<string,ImageInfo>();
        for (int i = 0; i < imageCount; i++)
        {
            images[RandomString()] = new() { Value = _random.NextSingle(), Size = _random.Next(minImageSize, maxImageSize) };
        }

        return images;
    }

    class ImageInfo:IDisposable
    {
        public int Size { get; set; }
        public float Value { get; set; }

        public void Dispose()
        {
        }
    }

    private static readonly Random _random = new();


    static string RandomString()
    {
        const int length = 10;
        var str = string.Empty;
        for (int i = 0; i < length; i++)
        {
            str += (char)_random.Next(65, 90);
        }

        return str;
    }



    static bool AreSimilar(ImageInfo x, ImageInfo y) => Math.Abs(y.Value-x.Value)<.1f;
    record Comparison<T>(T Path1, T Path2)
    {
        public bool Contains(T path) => Path1.Equals(path) || Path2.Equals(path);


        public T Other(T path)
        {
            if (Path1.Equals(path)) return Path2;
            if (Path2.Equals(path)) return Path1;
            throw new Exception("invalid path");
        }

        public bool IsEquivalentTo(Comparison<T> other) => (other.Path1.Equals(Path1) && other.Path2.Equals(Path2)) ||
                                                           (other.Path2.Equals(Path1) && other.Path1.Equals(Path2));
    }
    static IEnumerable<(TItem, TItem)> GetPairs<TSource, TItem>(
        IReadOnlyList<TSource> source,
        Func<TSource, long> sizeSelector,
        Func<TSource, TItem> itemLoader,
        long maxConcurrentSize) where TItem : IDisposable
    {

        var itemCount = source.Count;
        var comparisons = new List<Comparison<TSource>>(itemCount * itemCount / 2);
        for (int i = 0; i < itemCount - 1; i++)
        {
            for (int j = i + 1; j < itemCount; j++)
            {
                comparisons.Add(new(source[i], source[j]));
            }
        }

        return GetPairs(comparisons,sizeSelector,itemLoader,maxConcurrentSize);
    }

    static IEnumerable<(TItem, TItem)> GetPairs<TSource,TItem>(List<Comparison<TSource>> remainingComparisons,
        Func<TSource, long> sizeSelector,
        Func<TSource, TItem> itemLoader,
        long maxConcurrentSize) where TItem:IDisposable
    {
        if(!remainingComparisons.Any()) yield break;
        var images = LoadImages(remainingComparisons,sizeSelector,itemLoader,maxConcurrentSize);//load as many images as possible from the remaining comparisons
        var imageCount = images.Count;
        for (int i = 0; i < imageCount - 1; i++)
        {
            var (path1, image1) = images[i];
            for (int j = i + 1; j < imageCount; j++)
            {
                var (path2, image2) = images[j];
                yield return new(image1, image2);
                var comparison = new Comparison<TSource>(path1, path2);
                remainingComparisons.RemoveAll(c => c.IsEquivalentTo(comparison));
            }
        }

        //dispose
        foreach (var image in images.Select(i => i.Image))
        {
            image.Dispose();
        }

        images = null;//allow GC
        foreach (var pair in GetPairs(remainingComparisons,sizeSelector,itemLoader,maxConcurrentSize))
        {
            yield return pair;
        }
    }

    /// <summary>
    /// Loads as many images into memory as possible from the remaining comparison list
    /// </summary>
    static List<(TSource Path, TITem Image)> LoadImages<TSource,TITem>(List<Comparison<TSource>> remainingComparisons, Func<TSource, long> sizeSelector,
        Func<TSource, TITem> itemLoader,
        long maxConcurrentSize)
    {
        var availableMemory = maxConcurrentSize;
        remainingComparisons = remainingComparisons.ToList();//copy the list so we can alter it in local scope
        var loadedImages = new List<(TSource Path, TITem Image)>();
        if (!TryGetSeed(out var seed)) throw new Exception("One of the images is too large to load into memory");
        while (remainingComparisons.Any())
        {
            if (!remainingComparisons.TryGetFirst(c => c.Contains(seed),out var comparison ))
            {
                if (!TryGetSeed(out seed)) break;
                continue;
            }

            var other = comparison.Other(seed);
            remainingComparisons.Remove(comparison);
            if (!TryLoad(other)) break;

        }

        return loadedImages;

        bool TryLoad(TSource path)
        {
            var fileSize = sizeSelector(path);
            if (fileSize > availableMemory) return false;
            loadedImages.Add(new(path, itemLoader(path)));
            availableMemory -= fileSize;
            return true;
        }

        bool TryGetSeed(out TSource seed)
        {
            //first, remove any comparisons that are relevant to the current loaded list
            var loadedImageCount = loadedImages.Count;
            for (int i = 0; i < loadedImageCount-1; i++)
            {
                for (int j = 1; j < loadedImageCount; j++)
                {
                    var localComparison = new Comparison<TSource>(loadedImages[i].Path, loadedImages[j].Path);
                    remainingComparisons.RemoveAll(c => c.IsEquivalentTo(localComparison));
                }
            }

            if (!remainingComparisons.TryGetFirst(out var firstRemaining))
            {
                seed = default;
                return false;
            }

            seed = firstRemaining.Path1;
            return TryLoad(seed);
        }

  


    }
}
public static class Extensions
{
    public static bool TryGetFirst<T>(this IEnumerable<T> items, out T value) =>
        items.TryGetFirst(t => true, out value);
    public static bool TryGetFirst<T>(this IEnumerable<T> items, Predicate<T> condition, out T value)
    {
        foreach (var item in items)
        {
            if (condition(item))
            {
                value = item;
                return true;
            }
        }
        value = default;
        return false;
    }
}

为了回答你的问题,忽略了时间复杂度。目前的实施TryGetSeed使得时间复杂度为O(N^3),但这可以很容易地改进。我怀疑可以在 O(N^2) 时间内编写相同的算法,这是该问题的最佳时间复杂度。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用可用内存有效地比较 1,000 张图像 的相关文章

  • 为什么两个不同的 Base64 字符串的转换会返回相等的字节数组?

    我想知道为什么从 base64 字符串转换会为不同的字符串返回相同的字节数组 const string s1 dg const string s2 dq byte a1 Convert FromBase64String s1 byte a2
  • 秒表有最长运行时间吗?

    多久可以Stopwatch在 NET 中运行 如果达到该限制 它会回绕到负数还是从 0 重新开始 Stopwatch Elapsed返回一个TimeSpan From MSDN https learn microsoft com en us
  • Asp.NET WebApi 中类似文件名称的路由

    是否可以在 ASP NET Web API 路由配置中添加一条路由 以允许处理看起来有点像文件名的 URL 我尝试添加以下条目WebApiConfig Register 但这不起作用 使用 URIapi foo 0de7ebfa 3a55
  • 为什么当实例化新的游戏对象时,它没有向它们添加标签? [复制]

    这个问题在这里已经有答案了 using System Collections using System Collections Generic using UnityEngine public class Test MonoBehaviou
  • 使用实体框架模型输入安全密钥

    这是我今天的完美想法 Entity Framework 中的强类型 ID 动机 比较 ModelTypeA ID 和 ModelTypeB ID 总是 至少几乎 错误 为什么编译时不处理它 如果您使用每个请求示例 DbContext 那么很
  • 从Web API同步调用外部api

    我需要从我的 Web API 2 控制器调用外部 api 类似于此处的要求 使用 HttpClient 从 Web API 操作调用外部 HTTP 服务 https stackoverflow com questions 13222998
  • HTTPWebResponse 响应字符串被截断

    应用程序正在与 REST 服务通信 Fiddler 显示作为 Apps 响应传入的完整良好 XML 响应 该应用程序位于法属波利尼西亚 在新西兰也有一个相同的副本 因此主要嫌疑人似乎在编码 但我们已经检查过 但空手而归 查看流读取器的输出字
  • 如何从 appsettings.json 文件中的对象数组读取值

    我的 appsettings json 文件 StudentBirthdays Anne 01 11 2000 Peter 29 07 2001 Jane 15 10 2001 John Not Mentioned 我有一个单独的配置类 p
  • Java Integer CompareTo() - 为什么使用比较与减法?

    我发现java lang Integer实施compareTo方法如下 public int compareTo Integer anotherInteger int thisVal this value int anotherVal an
  • WCF 中 SOAP 消息的数字签名

    我在 4 0 中有一个 WCF 服务 我需要向 SOAP 响应添加数字签名 我不太确定实际上应该如何完成 我相信响应应该类似于下面的链接中显示的内容 https spaces internet2 edu display ISWG Signe
  • 如何设计以 char* 指针作为类成员变量的类?

    首先我想介绍一下我的情况 我写了一些类 将 char 指针作为私有类成员 而且这个项目有 GUI 所以当单击按钮时 某些函数可能会执行多次 这些类是设计的单班在项目中 但是其中的某些函数可以执行多次 然后我发现我的项目存在内存泄漏 所以我想
  • 什么时候虚拟继承是一个好的设计? [复制]

    这个问题在这里已经有答案了 EDIT3 请务必在回答之前清楚地了解我要问的内容 有 EDIT2 和很多评论 有 或曾经 有很多答案清楚地表明了对问题的误解 我知道这也是我的错 对此感到抱歉 嗨 我查看了有关虚拟继承的问题 class B p
  • 如何查看网络连接状态是否发生变化?

    我正在编写一个应用程序 用于检查计算机是否连接到某个特定网络 并为我们的用户带来一些魔力 该应用程序将在后台运行并执行检查是否用户请求 托盘中的菜单 我还希望应用程序能够自动检查用户是否从有线更改为无线 或者断开连接并连接到新网络 并执行魔
  • 链接器错误:已定义

    我尝试在 Microsoft Visual Studio 2012 中编译我的 Visual C 项目 使用 MFC 但出现以下错误 error LNK2005 void cdecl operator new unsigned int 2
  • 将控制台重定向到 .NET 程序中的字符串

    如何重定向写入控制台的任何内容以写入字符串 对于您自己的流程 Console SetOut http msdn microsoft com en us library system console setout aspx并将其重定向到构建在
  • 哪种 C 数据类型可以表示 40 位二进制数?

    我需要表示一个40位的二进制数 应该使用哪种 C 数据类型来处理这个问题 如果您使用的是 C99 或 C11 兼容编译器 则使用int least64 t以获得最大的兼容性 或者 如果您想要无符号类型 uint least64 t 这些都定
  • 如何将服务器服务连接到 Dynamics Online

    我正在修改内部管理应用程序以连接到我们的在线托管 Dynamics 2016 实例 根据一些在线教程 我一直在使用OrganizationServiceProxy out of Microsoft Xrm Sdk Client来自 SDK
  • C# - OutOfMemoryException 在 JSON 文件上保存列表

    我正在尝试保存压力图的流数据 基本上我有一个压力矩阵定义为 double pressureMatrix new double e Data GetLength 0 e Data GetLength 1 基本上 我得到了其中之一pressur
  • Windows 和 Linux 上的线程

    我在互联网上看到过在 Windows 上使用 C 制作多线程应用程序的教程 以及在 Linux 上执行相同操作的其他教程 但不能同时用于两者 是否存在即使在 Linux 或 Windows 上编译也能工作的函数 您需要使用一个包含两者的实现
  • 如何实现仅当可用内存较低时才将数据交换到磁盘的写缓存

    我想将应用程序生成的数据缓存在内存中 但如果内存变得稀缺 我想将数据交换到磁盘 理想情况下 我希望虚拟机通知它需要内存并将我的数据写入磁盘并以这种方式释放一些内存 但我没有看到任何方法以通知我的方式将自己挂接到虚拟机中before an O

随机推荐

  • 如何将 Red5 与 Asp.net 结合使用

    我想在线录制语音 我想我需要使用FMS或Red5 但我不知道如何将Red5与Asp net一起使用 实际上这是我第一次尝试处理这样的事情 目前我是一名 net开发人员 所以请有人告诉我一种处理它的方法 并告诉我如何将 Red5 与 Asp
  • 正则表达式:如何匹配包含重复模式的字符串?

    是否有一个正则表达式模式可以匹配包含重复模式的字符串 例如 a b c d y z 你有什么主意吗 也许您正在寻找这样的东西 这将匹配以逗号分隔的表单序列列表 where and 可以是任何字符
  • 使自定义 monad 转换器成为 MonadError 的实例

    我想让我的 monad 转换器成为一个实例MonadError如果转换后的单子是一个实例 基本上我希望我的变压器的行为与内置变压器一样 例如有一个MonadError实例为StateT MonadError e m gt MonadErro
  • 如何从另一个 sbt 项目引用外部 sbt 项目?

    我对 Scala 应用程序和通用核心库进行了以下设置 根 gt ApplicationA gt project gt build sbt gt CoreLibrary gt project gt build sbt 我想将 Applicat
  • 将 Yup 验证错误转换为可用对象

    Problem 我有一个 formik 表单 需要有 2 个不同的验证模式 具体取决于用户使用哪个按钮提交 我看到有些人说使用状态来决定哪个 但我想避免使用状态 因为在这种情况下感觉不对 我看过是的文档 https www npmjs co
  • 格式化整数时 printf 中的精度字段

    当我执行这两行时 printf 5d n 3 use of precision filed printf 05d n 3 use of 0 flag to prepend with 0 我得到以下输出 00003 00003 结果相同 所以
  • Google 字体无法在移动设备中加载

    我读过类似的帖子 但这个问题有点不同 我有 rest of the code 在 css 样式文件中我有 body font family Source Sans Pro sans serif rest of the code 它在浏览器中
  • 将新对象附加到 JSON 文件中的数组

    如何将附加对象添加到现有 JSON 文件 即对象数组 中 这是我的 JS 代码 const fs require fs let Human Name John age 20 Human JSON stringify Human null 2
  • 自动完成搜索字符串的多个部分,然后返回最可能的部分

    有点像这个问题 https stackoverflow com questions 824144 how do i use jquery autocomplete for multiple words 我有很多文本片段 每天都会使用很多很多
  • 使用 nokogiri 干式搜索网站的每个页面

    我想搜索网站的每个页面 我的想法是找到页面上保留在域内的所有链接 访问它们 然后重复 我也必须采取措施 避免重复努力 所以开始很容易 page http example com nf Nokogiri HTML open page link
  • Azure Functions 中 PowerShell 脚本的选项在哪里

    我想使用 PowerShell 创建 Azure Function 当我谈到 Azure 希望我选择要创建的函数类型时 唯一可用的语言是 C F 和 JavaScript 我错过了什么吗 如何使用 PowerShell 创建 Azure 函
  • 尝试使用 Comparator 按名称排序、忽略大小写以及先处理空值

    我在使用 Java 8 Comparator 类对项目列表进行排序时遇到问题 我当前的工作比较器如下 comparator Comparator comparing Person getName Comparator nullsFirst
  • Android 中从时间戳获取日期名称

    我有一个类 当它初始化时 它会使用公共 getter 在私有字段中记录初始化时间 public class TestClass private long mTimestamp public TestClass mTimestamp Syst
  • 每个 ajax 请求都会调用 preRenderView

    我正在使用 jquery waypoints 和 jsf 实现无限滚动link http kahimyang info kauswagan code blogs 1405 building a page with infinite scro
  • CSS自定义组合框问题

    我需要一个自定义组合框 所以 我实施了ul 问题是我无法通过单击在顶部打开组合框列表button 展示的同时ul 它移动button到网页底部 Code ul width 100px background color rgb 224 224
  • 在 Emacs 中定义新的工具提示

    我想向 emacs 添加自定义工具提示 更具体地说 每当我将鼠标悬停在符号 函数 变量 名称上时 用我的鼠标我想看到带有符号定义的工具提示 我知道我可以使用 cscope 这样的工具找到此类信息 但我不知道如何找到 将 cscope 的输出
  • 运行烘焙命令时出现 SQLSTATE HY000 2002

    我在运行烘焙命令时遇到问题 我认为它与 mysql 有关 但我在 Stackoverflow 上没有找到此错误的任何解决方案 这是我的app php Datasources gt default gt className gt Cake D
  • Kafka的消息键有什么特别的地方吗?

    我没有看到任何提及消息键 org apache kafka clients producer ProducerRecord key 除了它们可以用于主题分区 我可以自由地将我喜欢的任何数据放入密钥中 还是有一些我应该遵守的特殊语义 该密钥似
  • 分组时间序列(面板)数据的交叉验证

    我使用面板数据 随着时间的推移 我观察许多单位 例如人 对于每个单元 我都有相同固定时间间隔的记录 当将数据分为训练集和测试集时 我们需要确保这两个集是不相交的并且顺序的 即训练集中的最新记录应该在测试集中最早的记录之前 参见例如此博客文章
  • 如何使用可用内存有效地比较 1,000 张图像

    这是一个棘手的问题 我的磁盘中存储了大约 1 000 张图像 我想通过成对比较来找到彼此相似的图像 所以我必须做周围1 000 999 2 https stackoverflow com questions 46958633 generat