【问题标题】:Is it possible to use TDD with image processing algorithms?是否可以将 TDD 与图像处理算法一起使用?
【发布时间】:2009-08-15 23:57:16
【问题描述】:

最近,我在一个项目中工作,使用了 TDD(测试驱动开发)。该项目是一个用 Java 开发的 Web 应用程序,尽管对 Web 应用程序may not be trivial 进行单元测试,但可以使用模拟(我们使用了 Mockito 框架)。

现在我将开始一个项目,我将使用 C++ 进行图像处理(主要是图像分割),我不确定使用 TDD 是否是一个好主意。问题是分割的结果是否正确很难判断,同样的问题也适用于许多其他图像处理算法。

所以,我想知道这里是否有人成功地将TDD与图像分割算法(不一定是分割算法)一起使用。

【问题讨论】:

    标签: c++ unit-testing testing image-processing


    【解决方案1】:

    您至少可以将这些测试用于回归测试。例如,假设您有 5 个用于特定分割算法的测试图像。您通过代码运行 5 个图像并手动验证结果。结果正确时会存储在磁盘上的某个位置,并且这些测试的未来执行会将生成的结果与存储的结果进行比较。

    这样,如果您进行了重大更改,您就会发现它,但更重要的是,您只需经历一次(正确的)手动测试周期。

    【讨论】:

    • 不,你不能。您可能会发现一种算法总体上效果更好,但在 5 个单元测试图像上会产生不同的结果。因此,如果您继续改进算法,您的测试将非常脆弱。 (如果你不尝试改进它,那你为什么需要回归测试。)
    • @[nikie]:如果您更改算法以使其产生不同的结果,那么测试应该会中断。你/希望/回归测试是脆弱的,就像煤矿里的金丝雀。
    • 但这就是我的观点:回归测试对明确定义的问题有意义,例如图形算法。一旦它完成了它应该做的事情,它的行为就不应该改变,所以你进行回归测试。但是对于许多图像处理问题,您永远无法达到“正确性”的水平。 没有适用于所有情况的分段算法。所以有一天你可能不得不调整你的算法(这意味着不同的结果)来优化它。因此,除非您选择停止开发,否则回归测试将成为您的障碍。然后你也不需要回归测试
    • @[nikie]:我想我明白你的意思了,即使是最简单的改进,输出也可能过于脆弱,以至于测试无法生存。所以,如果可能的话,测试一些不那么脆弱的东西。例如,如果您的分割算法旨在识别图像中的某些内容,例如一组线条或肿瘤或面部,则对其进行测试,而不是聚合输出的像素级值,这些值可能会随着算法的改进而变化.后者将测试算法中使用的数学,而不是算法的应用(算法实现的“功能”)
    • 你可以这样使用TDD:得到一个算法,为它创建一些单元测试。然后你检查了它为几个图像产生的输出。如果运行良好,你可以确定这个算法会返回这个东西。为此做一个测试。然后你可以继续在其他地方做任何事情,运行这个测试会告诉你它仍然像以前一样工作(没有副作用)。当需要改进它 - 更新你的测试。当然,还有更多工作要做,但您可以确保算法始终按照您的预期工作。
    【解决方案2】:

    每当我进行任何与计算机视觉相关的开发时,TDD 几乎都是标准做法。你有图像和想要测量的东西。第一步是手动标记图像的(大)子集。这为您提供了测试数据。该过程(为了完全正确)然后将您的测试集分成两部分,一个“开发集”和一个“验证集”。您重复开发周期,直到您的算法在应用于开发集时足够准确。然后您在验证集上验证结果(这样您就不会在开发集的某些奇怪方面进行过度训练。 这是最纯粹的测试驱动开发。

    请注意,在开发此类严重依赖算法的软件时,您正在测试两种不同的东西。

    1. 您会在软件中遇到的常见错误。这些可以使用“正常”的 TDD 技术进行测试
    2. 您的算法的性能,为此您需要上述系统。

    根据 (1),一个程序可以没有错误,但根据 (2) 则不完全。例如,一个非常简单的图像分割算法说:“图像的左半部分是一个片段,右半部分是另一个片段。这个程序可以根据(1)很容易地做到无错误。完全是另一回事。它满足你的性能需求。不要混淆这两个方面,不要让一个干扰另一个。

    更具体地说,我建议您首先开发算法,然后将 TDD 与算法(而不是代码!)以及软件的其他要求作为单独的 TDD 开发过程的规范一起使用。在大量开发中的一些相当复杂的算法深处对小型临时辅助函数进行单元测试是浪费时间和精力。

    【讨论】:

      【解决方案3】:

      图像处理中的 TDD 仅对以下确定性问题有意义:

      • 图像算法
      • 直方图生成
      • 等等..

      但是 TDD 不适用于特征提取算法,例如:

      • 边缘检测
      • 细分
      • 角点检测

      ...因为没有算法可以完美地解决所有图像的此类问题。

      【讨论】:

        【解决方案4】:

        我认为您能做的最好的事情就是测试您的算法所包含的简单、数学上定义明确的构建块,例如线性滤波器、形态学运算、FFT、小波变换等。这些通常非常棘手,无法有效且正确地实现所有边界案例,因此验证它们确实有意义。

        对于像图像分割这样的实际算法,恕我直言,TDD 没有多大意义。我什至认为单元测试在这里没有意义。当然,你可以编写测试,但那些总是非常脆弱的。典型的图像处理算法需要一些参数,这些参数必须针对所需结果进行调整(这个过程不能自动化,也不能在算法工作之前完成)。分割算法的结果也没有很好的定义,但是您的单元测试只能测试一些定义明确的属性。算法可以在不做你想做的事情的情况下拥有该属性,或者反过来,所以你的测试结果不是很丰富。此外,要测试分割算法的结果,您需要编写大量非常难的代码,而直观地验证结果非常容易而且无论如何你都必须这样做

        我认为它在某种程度上类似于对用户界面进行单元测试:测试实际定义明确的功能(例如,当用户单击此按钮时,某些项目会添加到此列表中,并且此标签会显示该文本...)比较容易,可以省去很多工作和调试。但是世界上没有任何测试可以告诉你你的 UI 是否可用、易于理解或漂亮,因为这些东西没有很好的定义。

        【讨论】:

          【解决方案5】:

          我们就同一个“问题”进行了一些讨论,您的 cmets 中提到了许多评论,这些评论在此处的答案下方。

          我们走到了尽头,计算机视觉/图像处理(关于分割、检测或类似的全局目标)中的 TDD 可能是:

          1. 获取应处理的图像/序列并为该图像创建测试:所需的输出和衡量结果可能与“基本事实”相差多远的指标。

          2. 为不同的设置(不同的照明、不同的对象或类似的东西)获取另一个图像/序列,如果您的算法失败并为此编写测试。

          3. 改进您的算法以解决之前的所有测试。

          4. 回到2。

          不知道这是否适用,创建测试将比在传统 TDD 中复杂得多,因为可能很难定义您的基本事实和算法输出之间允许的差异。

          可能最好只使用一些 QualityDrivenDevelopment,您的更改不应该使事情变得“更糟”(您必须再次为此找到一个指标)。

          显然,您仍然可以对这些算法的确定性部分使用传统的单元测试,但这并不是“信号处理中的 TDD”的真正问题

          【讨论】:

          • +1。换言之,“测试数据”的不断增长“推动”了代码开发的进步。每个“测试数据”都是一个可能导致代码失败(不符合其设计目的)的输入。因此,“测试数据”在信号处理中取代了TDD中“测试方法”的角色。
          【解决方案6】:

          您在问题中描述的图像处理测试的水平远高于您将使用 TDD 编写的大多数测试。

          在真正的Test Driven Development 流程中,您将首先编写一个失败的测试,然后再向您的软件添加任何新功能,然后编写使测试通过、冲洗并重复的代码。

          这个过程产生了一个包含Unit Tests 的大型库,有时测试的LOC 比功能代码还多!

          由于您的分析算法具有结构化行为,因此它们非常适合 TDD 方法。

          但我认为您真正要问的问题是“我如何针对模糊图像处理软件执行一套Integration Tests?”你可能会认为我在扯皮,但是单元测试和集成测试之间的区别确实触及了测试驱动开发的核心。 TDD 过程的好处来自于单元测试的丰富支持结构。

          在您的情况下,我会将集成测试套件与针对 Web 应用程序的自动化性能指标进行比较。我们想要累积执行时间的历史记录,但我们可能不希望显式地使构建失败,因为单个执行性能不佳(这可能受到网络拥塞、磁盘 I/O 等的影响)。您可以围绕测试套件的性能设置一些宽松的容差,并让Continuous Integration 服务器踢出每日报告,让您对算法的性能有一个高层次的概述。

          【讨论】:

            【解决方案7】:

            我想说 TDD 在这样的应用程序中比在 Web 应用程序中更容易。您必须测试一个完全确定的算法。您不必担心用户输入和 HTML 呈现等模糊问题。

            您的算法由多个步骤组成。可以测试这些步骤中的每一个。如果你给他们固定的、已知的输入,它们应该会产生固定的、已知的输出。所以为此写一个测试。一般来说,您无法测试算法是否“正确”,但您可以为它提供您已经预先计算出正确结果的数据,这样您就可以验证它在 that中产生正确的输出> 案例。

            【讨论】:

            • 这可能是一个愚蠢的问题,但是当我必须编写的算法没有可用的实现时,如何获得预先计算的结果?
            • 我不知道算法,所以很难确定。但是你不需要完整算法的结果,只需要它的各个步骤。这些可以手动计算,也可以在 Matlab 或类似工具中计算。您可能会为您的代码提供一些预先确定的数据,然后手动验证输出是否正确。然后你就得到了可以在以后的测试中使用的预计算结果。
            【解决方案8】:

            我不是很喜欢你的问题,所以我不知道它的热点。但是,您的算法的最终结果希望是确定性的,因此您可以对其执行功能测试。当然,您必须确定“已知良好”的结果。我知道在图形库(确切地说是VTK)上执行的TDD。逐个像素地对最终结果图像进行比较。无需过多介绍,如果您有已知良好的结果,您可以对测试结果执行 md5 并将其与已知良好的 md5 进行比较。

            对于单元测试,我很确定您可以测试单个例程。这将迫使您拥有非常细粒度的开发风格。

            【讨论】:

            • 我认为的问题是要获得好的结果......也许我会研究VTK的人是如何做到的(如果VTK是一个开源库)。测试单个例程而不是生成的分割图像也可能是一个好主意。
            • 好吧,我想有一种方法可以验证您的结果。我同意这不是微不足道的。我是一名量子化学家,我知道要知道给定结果是否正确有多难,因为……你不知道“先验”。它只是有意义或没有,但是一个微妙的错误会导致错误的结果“有意义”并且会被认为是好的。在这种情况下,最好的策略是将您的结果与其他人实施的相同算法进行比较。如果两个完全不同的程序给你相同的结果,你可以对它有合理的信心。
            • 只有在你实现了别人已经实现的已知算法时才有效。在图像处理中,这种情况并不常见。
            【解决方案9】:

            可能想看看this paper

            【讨论】:

              【解决方案10】:

              如果您的目标是优化算法而不是验证正确性,您需要一个指标。一个好的指标可以衡量算法中的性能标准。对于分段算法,这可以是每个分段内像素数据的标准偏差的总和。使用该指标,您可以使用阈值水平或算法的排名版本。

              【讨论】:

                【解决方案11】:

                您可以使用具有 许多 个示例和正确结果的统计方法,然后测试运行所有这些示例并评估它们的算法。然后它会生成一个数字,即所有这些数字的组合成功率。

                这样您对特定故障的敏感度降低,并且您的测试更加稳健。

                然后您可以使用成功率的阈值来查看测试是否失败。

                【讨论】:

                  猜你喜欢
                  • 2017-06-19
                  • 2021-12-27
                  • 2017-07-23
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-12-20
                  • 1970-01-01
                  • 2015-09-20
                  相关资源
                  最近更新 更多