Преобразование цветового пространства PIL YCbCr -> RGB - PullRequest
13 голосов
/ 12 августа 2011

Алгоритм, используемый в PIL v1.1.7, дает «размытые» результаты. При преобразовании тех же исходных данных с использованием ffmpeg это выглядит правильно. Использование mplayer дает идентичные результаты для ffmpeg (возможно, они используют ту же библиотеку внизу). Это наводит меня на мысль, что PIL может запутывать преобразования цветового пространства. Похоже, преобразование получено в libImaging/ConvertYCbCr.c:

/*  JPEG/JFIF YCbCr conversions

    Y  = R *  0.29900 + G *  0.58700 + B *  0.11400
    Cb = R * -0.16874 + G * -0.33126 + B *  0.50000 + 128
    Cr = R *  0.50000 + G * -0.41869 + B * -0.08131 + 128

    R  = Y +                       + (Cr - 128) *  1.40200
    G  = Y + (Cb - 128) * -0.34414 + (Cr - 128) * -0.71414
    B  = Y + (Cb - 128) *  1.77200

*/

Это просто комментарий в исходном коде, конечно, это код на C, и фактическая функция реализована с помощью таблиц подстановки, а не умножения матриц (static INT16 R_Cr и т. Д. Для краткости):

void
ImagingConvertYCbCr2RGB(UINT8* out, const UINT8* in, int pixels)
{
    int x;
    UINT8 a;
    int r, g, b;
    int y, cr, cb;

    for (x = 0; x < pixels; x++, in += 4, out += 4) {

        y = in[0];
        cb = in[1];
        cr = in[2];
        a = in[3];

        r = y + ((           R_Cr[cr]) >> SCALE);
        g = y + ((G_Cb[cb] + G_Cr[cr]) >> SCALE);
        b = y + ((B_Cb[cb]           ) >> SCALE);

        out[0] = (r <= 0) ? 0 : (r >= 255) ? 255 : r;
        out[1] = (g <= 0) ? 0 : (g >= 255) ? 255 : g;
        out[2] = (b <= 0) ? 0 : (b >= 255) ? 255 : b;
        out[3] = a;
    }
}

Я гуглил, но, похоже, много путаницы в отношении «правильного» способа сделать это преобразование цветового пространства. Итак, мой вопрос: правильно ли это, а если нет, то как лучше?


edit: После прочтения ссылок, предоставленных Марком Рэнсомом, я обнаружил, что существуют противоречивые определения в зависимости от того, используете ли вы полный диапазон YCbCr или ограничиваетесь допустимым диапазоном. См. Ссылки ниже для получения дополнительной информации:

Кажется, в версии PIL используется неверный алгоритм, поэтому я применил собственную функцию преобразования, которая дает правильные результаты (версия "SDTV"). Код, включенный ниже, для будущих читателей, чтобы использовать:

from numpy import dot, ndarray, array

def yuv2rgb(im, version='SDTV'):
    """
    Convert array-like YUV image to RGB colourspace

    version:
      - 'SDTV':  ITU-R BT.601 version  (default)
      - 'HDTV':  ITU-R BT.709 version
    """
    if not im.dtype == 'uint8':
        raise TypeError('yuv2rgb only implemented for uint8 arrays')

    # clip input to the valid range
    yuv = ndarray(im.shape)  # float64
    yuv[:,:, 0] = im[:,:, 0].clip(16, 235).astype(yuv.dtype) - 16
    yuv[:,:,1:] = im[:,:,1:].clip(16, 240).astype(yuv.dtype) - 128

    if version.upper() == 'SDTV':
        A = array([[1.,                 0.,  0.701            ],
                   [1., -0.886*0.114/0.587, -0.701*0.299/0.587],
                   [1.,  0.886,                             0.]])
        A[:,0]  *= 255./219.
        A[:,1:] *= 255./112.
    elif version.upper() == 'HDTV':
        A = array([[1.164,     0.,  1.793],
                   [1.164, -0.213, -0.533],
                   [1.164,  2.112,     0.]])
    else:
        raise Exception("Unrecognised version (choose 'SDTV' or 'HDTV')")

    rgb = dot(yuv, A.T)
    result = rgb.clip(0, 255).astype('uint8')

    return result

1 Ответ

7 голосов
/ 12 августа 2011

Если вы посмотрите на определения Википедии, вы увидите, что есть два противоречивых определения для YCbCr. Определение ITU-R BT.601 сжимает значения в диапазоне 16-235, чтобы обеспечить пространство для ног и запас, а версия JPEG использует полный диапазон 0-255. Если бы вы декодировали значения в пространстве BT.601, используя формулу для JPEG, результат определенно выглядел бы размытым.

...