VsampFactor и HsampFactor в библиотеке FJCore

Я использовал библиотеку FJCore в проекте Silverlight, чтобы помочь с некоторой обработкой изображений в реальном времени, и я пытаюсь выяснить, как получить немного больше сжатия и производительности из библиотеки. Теперь, насколько я понимаю, стандарт JPEG позволяет указывать коэффициент подвыборки цветности (см. http://en.wikipedia.org/wiki/Chroma_subsampling и http://en.wikipedia.org/wiki/Jpeg); и кажется, что это должно быть реализовано в библиотеке FJCore с использованием массивов HsampFactor и VsampFactor:

    public static readonly byte[] HsampFactor = { 1, 1, 1 };
    public static readonly byte[] VsampFactor = { 1, 1, 1 };

Однако мне трудно понять, как их использовать. Мне кажется, что текущие значения должны представлять 4:4:4 подвыборки (например, никакой выборки вообще нет), и что если бы я хотел получить 4:1:1, то правильные значения были бы примерно такими:

    public static readonly byte[] HsampFactor = { 2, 1, 1 };
    public static readonly byte[] VsampFactor = { 2, 1, 1 };

По крайней мере, именно так другие подобные библиотеки используют эти значения (например, см. Пример кода здесь для libjpeg).

Однако ни приведенные выше значения {2, 1, 1}, ни какой-либо другой набор значений, которые я пробовал, кроме {1, 1, 1}, не дают четкого изображения. И при взгляде на код не похоже, что он так написан. Но я не могу понять, что на самом деле пытается сделать код FJCore. Кажется, что он просто использует факторы выборки для повторения операций, которые уже выполнены - то есть, если бы я не знал лучше, я бы сказал, что это ошибка. Но это довольно устоявшаяся библиотека, основанная на некотором достаточно хорошо зарекомендовавшем себя коде Java, поэтому я был бы удивлен, если бы это было так.

Кто-нибудь есть какие-либо предложения о том, как использовать эти значения для получения подвыборки цветности 4:2:2 или 4:1:1?

Для чего это стоит, вот соответствующий код из класса JpegEncoder:

for (comp = 0; comp < _input.Image.ComponentCount; comp++)
{
    Width = _input.BlockWidth[comp];
    Height = _input.BlockHeight[comp];

    inputArray = _input.Image.Raster[comp];

    for (i = 0; i < _input.VsampFactor[comp]; i++)
    {
        for (j = 0; j < _input.HsampFactor[comp]; j++)
        {
            xblockoffset = j * 8;
            yblockoffset = i * 8;
            for (a = 0; a < 8; a++)
            {
                // set Y value.  check bounds
                int y = ypos + yblockoffset + a; if (y >= _height) break;

                for (b = 0; b < 8; b++)
                {
                    int x = xpos + xblockoffset + b; if (x >= _width) break;
                    dctArray1[a, b] = inputArray[x, y];
                }
            }
            dctArray2 = _dct.FastFDCT(dctArray1);
            dctArray3 = _dct.QuantizeBlock(dctArray2, FrameDefaults.QtableNumber[comp]);
            _huf.HuffmanBlockEncoder(buffer, dctArray3, lastDCvalue[comp], FrameDefaults.DCtableNumber[comp], FrameDefaults.ACtableNumber[comp]);
            lastDCvalue[comp] = dctArray3[0];
        }
    }
}

И обратите внимание, что в циклах i & j они не управляют каким-либо пропуском пикселей: если для HsampFactor[0] установлено значение два, он просто захватывает два блока вместо одного.

1 ответ

Решение

Я понял. Я думал, что, устанавливая коэффициенты выборки, вы предлагаете библиотеке выполнить выборку самих компонентов растра. Оказывается, что когда вы устанавливаете коэффициенты выборки, вы фактически указываете библиотеке относительный размер предоставляемых вами растровых компонентов. Другими словами, вам нужно выполнить подвыборку цветности изображения самостоятельно, прежде чем вы отправите его в библиотеку FJCore для сжатия. Нечто подобное вот что ищет:

    private byte[][,] GetSubsampledRaster()
    {
        byte[][,] raster = new byte[3][,];
        raster[Y] = new byte[width / hSampleFactor[Y], height / vSampleFactor[Y]];
        raster[Cb] = new byte[width / hSampleFactor[Cb], height / vSampleFactor[Cb]];
        raster[Cr] = new byte[width / hSampleFactor[Cr], height / vSampleFactor[Cr]];

        int rgbaPos = 0;
        for (short y = 0; y < height; y++)
        {
            int Yy = y / vSampleFactor[Y];
            int Cby = y / vSampleFactor[Cb];
            int Cry = y / vSampleFactor[Cr];
            int Yx = 0, Cbx = 0, Crx = 0;
            for (short x = 0; x < width; x++)
            {
                // Convert to YCbCr colorspace.
                byte b = RgbaSample[rgbaPos++];
                byte g = RgbaSample[rgbaPos++];
                byte r = RgbaSample[rgbaPos++];
                YCbCr.fromRGB(ref r, ref g, ref b);

                // Only include the byte in question in the raster if it matches the appropriate sampling factor.
                if (IncludeInSample(Y, x, y))
                {
                    raster[Y][Yx++, Yy] = r;
                }
                if (IncludeInSample(Cb, x, y))
                {
                    raster[Cb][Cbx++, Cby] = g;
                }
                if (IncludeInSample(Cr, x, y))
                {
                    raster[Cr][Crx++, Cry] = b;
                }

                // For YCbCr, we ignore the Alpha byte of the RGBA byte structure, so advance beyond it.
                rgbaPos++;
            }
        }
        return raster;
    }

    static private bool IncludeInSample(int slice, short x, short y)
    {
        // Hopefully this gets inlined . . . 
        return ((x % hSampleFactor[slice]) == 0) && ((y % vSampleFactor[slice]) == 0);
    }

Могут быть дополнительные способы оптимизировать это, но пока это работает.

Другие вопросы по тегам