如何高效地将平面图像转换为交错图像(C++)?[已关闭]

ttisahbt  于 2022-12-01  发布在  其他
关注(0)|答案(5)|浏览(190)

**已关闭。**此问题不符合Stack Overflow guidelines。当前不接受答案。

我们不允许问题寻求书籍、工具、软件库等的建议。您可以编辑问题,以便使用事实和引文来回答。
去年关闭了。
这篇文章3天前被编辑并提交审查。
Improve this question
我正在开发一个视频播放应用程序,它将逐帧显示包含原始平面图像数据的视频文件。它包含的数据是8位rgb(目前没有alpha).这里的硬件只接受隔行扫描的图像数据,因此我需要将平面图像数据转换成隔行扫描的图像数据,我所做的是对平面数据进行memmove或memcpy,但是在处理高清内容时,由于数据是原始数据,这将占用大量时间。我尝试使用两个线程来实现,一个用于处理,另一个用于显示。结果表明,显示处理过的交错数据确实很快,但处理线程无法跟上。因此,由于处理速度的原因,帧速率受到严重影响。
我确实有个想法,就是对所有内容进行预处理并保存在内存中(那些视频片段的帧数相对较少)。当需要时,我会将处理后的数据显示在内存中。实际上我测试了这种方法,它相当快(60 fps)。然而,这似乎是次优的,因为我要么会有一个真正缓慢的第一次运行,要么需要等待一段时间才开始播放。而且,当文件变大时,由于内存限制,无法做到这一点。
所以我正在寻找任何图像处理库或算法,做平面-〉交错快速。我确实尝试了gil从boost,但性能不是很好。

r1zk6ea1

r1zk6ea11#

我不得不解决同样的问题,但我有一个额外的约束,我需要执行转换“在适当的地方”(即,我必须离开图像数据在同一个缓冲区)。在下面的图像中,我演示了像素需要如何从平面移动到交错表示:

所以我们可以通过一系列的交换来改变图像。这是我的C++实现,它在线性时间内运行。模板参数T是图像通道类型(例如,uint8_t表示字节大小的通道)。

#include <vector>
#include <cstdint>
#include <algorithm>

template <typename T>
void planarToInterleaved(int numPixels, int numChannels, T pixels[]) {
    const int size = numPixels * numChannels;
    std::vector<bool> visited(size);
    std::fill(visited.begin(), visited.end(), false);

    auto nextUnvisited = [&](int index) -> int {
        int i;
        for (i = index; i < size && visited[i]; i++)
            ;
        return i;
    };

    auto interleavedIndex = [=](int planarIndex) -> int {
        const int i = planarIndex % numPixels;
        const int k = planarIndex / numPixels;
        return numChannels*i + k;
    };

    int J = 0;
    int Jnext = 0;
    while ( (J = nextUnvisited(Jnext++)) < size ) {
        visited[J] = true;
        const int Jstart = J;
        T tmp = pixels[J];
        while ( true ) {
            const int I = interleavedIndex(J);
            if ( I == J ) break; // 1-node cycle
            std::swap(pixels[I],tmp);
            if ( I == Jstart ) break;
            J = I;
            visited[J] = true;
        }
    }
}

这里我将存储在缓冲区image(保存WH3个值)中的WxH RGB图像从平面转换为隔行扫描:

planarToInterleaved(W*H, 3, image);

不管怎么说,这很有趣。

4ngedf3f

4ngedf3f2#

(在我的注解旁边添加代码)
这是在2.4GHz英特尔酷睿2双核处理器上用g++ 4.2.1和-O2编译的,在10秒内运行2000帧。

int const kWidth = 1920;
int const kHeight = 1080;
for (std::size_t i = 0; i != kWidth*kHeight; ++i) {
    interleavedp[i*3+0] = planarp[i+0*kWidth*kHeight];
    interleavedp[i*3+1] = planarp[i+1*kWidth*kHeight];
    interleavedp[i*3+2] = planarp[i+2*kWidth*kHeight];
}

注意,这样写可以让编译器更好地优化,而把它分成几行(或12字节的块)只会让运行速度变慢。

57hvy0tb

57hvy0tb3#

libswscale(ffmpeg的一部分)可以做到这一点,据我所知,一个很好的教程可以找到here

6yjfywim

6yjfywim4#

使用矢量内部函数编写此函数应该非常简单。我不知道您使用的是什么处理器、编译器或压缩像素格式,因此我将使用x86的愚者和MMX内部函数给出一个示例实现。将此代码转换为ARM NEON、PowerPC Altivec或x86/x64 SSE代码也应该很容易。
这应该可以将RGB平面转换为32位RGBA压缩,尽管ARGB实际上更常见。如果你需要24位RGB,你就得有点创意。在编写这段代码时,处理器的《软件开发手册》将是你最好的朋友,你也需要阅读编译器的文档。
SIMD非常好地处理了这一点,你可以从下面的代码有多短来判断。注意,下面的代码实际上是C99,而不是C++,因为C99可以访问restrict关键字,这可以减少生成的加载和存储的数量。
还请注意,此代码具有严格的对齐要求。

#include <stddef.h>

#if defined(USE_MMX)

typedef char v8qi __attribute__ ((vector_size(8)));
void pack_planes3(void *dest, const void *src[3], size_t n)
{
    v8qi *restrict dp = dest, x, y, zero = { 0, 0, 0, 0, 0, 0, 0, 0 };
    const v8qi *restrict sp1 = src[0];
    const v8qi *restrict sp2 = src[1];
    const v8qi *restrict sp3 = src[2];
    size_t i;
    for (i = 0; i < n; i += 8) {
        x = __builtin_ia32_punpckhbw(*sp1, *sp3);
        y = __builtin_ia32_punpckhbw(*sp2, zero);
        dp[0] = __builtin_ia32_punpckhbw(x, y);
        dp[1] = __builtin_ia32_punpcklbw(x, y);
        x = __builtin_ia32_punpcklbw(*sp1, *sp3);
        y = __builtin_ia32_punpcklbw(*sp2, zero);
        dp[2] = __builtin_ia32_punpckhbw(x, y);
        dp[3] = __builtin_ia32_punpcklbw(x, y);
        sp1++;
        sp2++;
        sp3++;
        dp += 4;
    }
}

#else

/* Scalar implementation goes here */

#endif
von4xj4u

von4xj4u5#

有一个Simd Library,它有很多图像转换的算法,它支持以下图像格式之间的转换:NV 12、YUV 420 P、YUV 422 P、YUV 444 P、BGR-24、BGRA-32、HSL-24、HSV-24、Gray-8、Bayer等。这些算法通过使用不同的SIMD CPU扩展进行了优化。特别是,该库支持以下CPU扩展:适用于x86/x64的SSE、SSE 2、SSSE 3、SSE4.1、SSE4.2、AVX和AVX 2,以及适用于PowerPC的VMX(Altivec)和VSX(Power 7)。

相关问题