使用Emgu CV迭代图像像素

18
我想要迭代所有图像像素并与搜索模式进行比较。使用 C# 编写,希望获得最佳性能。我找到了 Emgu CV,这是 Intel OpenCV 的一个包装器,但我不知道如何正确使用 Emgu。有谁知道我该怎么做吗?Image 对象的 Data 属性是什么?它是否是图像数据?如果是,哪个值是什么?谢谢。
更新:
我已经在 C# 中编写了我的功能,但速度太慢了!我已经有一个用 C 编写的算法,我将其翻译成了 C#。C# 比 C 慢 3 到 4 倍!(我的函数几乎迭代每个图像像素以在图像中寻找形状。-> Hugh 变换)
好吧,我听说不安全的代码可能会更快,因为它不检查数组边界等等。这是真的吗?不安全的代码直接在物理机器上运行吗?
无论如何,我试图将不安全的代码放入我的功能中。但我无法获取指向我的三维数组的指针或使用指针访问三维数组。我该如何使用不安全代码重新编写上面的代码?这样做会带来额外的性能提升吗?甚至可以像 C 代码一样快吗?
2个回答

58

根据Emgu网站的说法,主要有两种策略:

安全(慢)方式

假设您正在处理一个 Image<Bgr, Byte>。 您可以通过调用以下方法来获取第y行第x列的像素:

Bgr color = img[y, x];

在第y行和第x列上设置像素也很简单。

img[y,x] = color;

一种快速的方法

图像像素值存储在Data属性中,这是一个3D数组。 好的,这是真实的情况,但并没有说明如何在实际场景中做到这一点。让我们看一些可行的代码,然后讨论性能和优化:

Image<Bgr, Byte> original = newImage<Bgr, byte>(1024, 768);
Stopwatch evaluator = newStopwatch(); 

int repetitions = 20;
Bgr color = newBgr(100, 40, 243);

evaluator.Start();

for (int run = 0; run < repetitions; run++)
{
    for (int j = 0; j < original.Cols; j++)
    {
        for (int i = 0; i < original.Rows; i++)
        {
            original[i, j] = color;
        }
    }
}

evaluator.Stop();
Console.WriteLine("Average execution time for {0} iteration \n using column per row access: {1}ms\n", repetitions, evaluator.ElapsedMilliseconds / repetitions);

这是使用安全的缓慢的设置图像像素的方法,在20次运行后得到的平均运行时间。在我的计算机上,需要1021毫秒...

因此,在循环和设置等于1024*768像素数量时,平均时间为1021毫秒。我们可以通过逐行循环来稍微改进一下。

因此,让我们稍微重构一下代码,并使用直接的Image.Data属性来加快速度:

evaluator.Reset();
evaluator.Start();

for (int run = 0; run < repetitions; run++)
{
    for (int i = 0; i < original.Rows; i++)
    {
        for (int j = 0; j < original.Cols; j++)
        {
            original.Data[i, j, 0] = 100;
            original.Data[i, j, 1] = 40;
            original.Data[i, j, 2] = 243;
        }
    }
}

evaluator.Stop();
Console.WriteLine("Average execution time for {0} iterations \n using Data property: {1}ms\n", repetitions, evaluator.ElapsedMilliseconds / repetitions);

在我的电脑上,执行时间为519毫秒。 因此我们获得了50%的性能提升。执行时间缩短了一倍。

所以仔细查看代码并牢记我们使用的是C#,我们可以进行一个小改变,从而大幅度地提高图片像素设置的性能... 我们不应该在循环内部使用C#属性!!!

evaluator.Reset();
evaluator.Start();

byte[,,] data = original.Data;

for (int run = repetitions - 1; run >= 0; run--)
{
    for (int i = original.Rows - 1; i >= 0; i--)
    {
        for (int j = original.Cols - 1; j >= 0; j--)
        {
            data[i, j, 0] = 100;
            data[i, j, 1] = 40;
            data[i, j, 2] = 243;
        }
    }
}

evaluator.Stop();

使用这段最新的代码,由于正确地使用C#语言,您将获得巨大的性能提升(73ms)


2
使用数据属性没有任何不安全性。开发人员需要更加注意指定正确的索引来获取或设置正确的值,因为数据属性是一个三维数组。 - Luca Del Tongo
为什么时间突然减少了?看起来编译器需要在这里进行一些优化。这是C# 5吗? - Pedro77
@Pedro77 即使 Data 不执行任何验证或细致的分支(这很可能不会发生),除非底层值被标记为 readonly,否则编译器将无法轻易地知道是否有另一个线程可能已更改其引用的内容。 - Rei Miyasaka
同时,这仍然没有使用unsafe,因此您仍然会承担边界检查的成本。 - Rei Miyasaka
3
你为什么要翻转范围?即在第一个片段中,你迭代从[0到行数],然后你又从[行数到0]迭代。 - manatttta
显示剩余5条评论

0

我想要补充@Luce Del Tongo和@Dent7777已经详细回答的内容,提供使用emgucv对图像数据进行每个值操作的另一种解决方案。虽然我的测试(相同的方法)显示出与上面的最终解决方案相当的性能,但我认为这是一个有价值的替代方案。

Image.Convert()方法有许多强大的重载,可以用于在调用图像上应用委托函数以每个值返回相同或不同TDepth的结果图像。请参见http://www.emgu.com/wiki/index.php/Working_with_Images中的“通用操作”以获取更多详细信息。

最重要的是能够将最多三个附加图像作为参数传递给Convert方法,其中包含的数据将传递给委托。

例如,一个复杂的滤波器:

// From the input image "original"
Image<Bgr, Byte> original = new Image<Bgr, byte>(1024, 768);

// I wish to produce an image "result"
Image<Gray, double> result = new Image<Gray, double>(1024, 768);

// Whereby each "result" pixel is
Bgr originalPixel;
double resultPixel =
(0.5 + Math.Log(originalPixel.Green) -
(0.5 * Math.Log(originalPixel.Red)) -
(0.5 * Math.Log(originalPixel.Blue)));

使用之前答案中提供的解决方案:

byte[,,] sourceData = original.Data;
double[,,] resultData = result.Data;
for (int i = original.Rows - 1; i >= 0; i--)
{
    for (int j = original.Cols - 1; j >= 0; j--)
    {
        resultData[i,j,0] =
        (0.5 + Math.Log(sourceData[i, j, 1]) -
        (0.5 * Math.Log(sourceData[i, j, 2])) -
        (0.5 * Math.Log(sourceData[i, j, 0])));
    }
}

我的机器平均时间:(676毫秒)

或者,我可以在原始图像的蓝色通道上调用Convert方法,并将另外两个通道作为参数传递,同时传递一个相应签名的委托方法来执行计算:

result =
    original[0].Convert<byte, byte, double>(
    original[1],
    original[2],
    Compute);

在哪里:

private double Compute(byte B, byte G, byte R)
{
    return (0.5 + Math.Log(G) - (0.5 * Math.Log(R)) - (0.5 * Math.Log(B)));
}

我的机器平均时间:(674毫秒)

也许只是个人偏好,但我希望这对某个地方的某个人有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接