it-swarm.com.ru

Сдвиг оттенка цвета RGB

Я пытаюсь написать функцию, чтобы сместить оттенок цвета RGB. В частности, я использую его в приложении для iOS, но математика универсальна.

На графике ниже показано, как значения R, G и B изменяются относительно оттенка.

Graph of RGB values across hues

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

Вот то, что я имею до сих пор, какой вид работ. Он отлично работает, если вы переходите от чистого желтого, голубого или пурпурного, но в некоторых местах он становится немного шатким.

Color4f ShiftHue(Color4f c, float d) {
    if (d==0) {
        return c;
    }
    while (d<0) {
        d+=1;
    }

    d *= 3;

    float original[] = {c.red, c.green, c.blue};
    float returned[] = {c.red, c.green, c.blue};

    // big shifts
    for (int i=0; i<3; i++) {
        returned[i] = original[(i+((int) d))%3];
    }
    d -= (float) ((int) d);
    original[0] = returned[0];
    original[1] = returned[1];
    original[2] = returned[2];

    float lower = MIN(MIN(c.red, c.green), c.blue);
    float upper = MAX(MAX(c.red, c.green), c.blue);

    float spread = upper - lower;
    float shift  = spread * d * 2;

    // little shift
    for (int i = 0; i < 3; ++i) {
        // if middle value
        if (original[(i+2)%3]==upper && original[(i+1)%3]==lower) {
            returned[i] -= shift;
            if (returned[i]<lower) {
                returned[(i+1)%3] += lower - returned[i];
                returned[i]=lower;
            } else
                if (returned[i]>upper) {
                    returned[(i+2)%3] -= returned[i] - upper;
                    returned[i]=upper;
                }
            break;
        }
    }

    return Color4fMake(returned[0], returned[1], returned[2], c.alpha);
}

Я знаю, что вы можете сделать это с UIColors и изменить цвет на что-то вроде этого:

CGFloat hue;
CGFloat sat;
CGFloat bri;
[[UIColor colorWithRed:parent.color.red green:parent.color.green blue:parent.color.blue alpha:1] getHue:&hue saturation:&sat brightness:&bri alpha:nil];
hue -= .03;
if (hue<0) {
    hue+=1;
}
UIColor *tempColor = [UIColor colorWithHue:hue saturation:sat brightness:bri alpha:1];
const float* components= CGColorGetComponents(tempColor.CGColor);
color = Color4fMake(components[0], components[1], components[2], 1);

но я не в восторге от этого, так как он работает только в iOS 5, и между выделением ряда цветных объектов и преобразованием из RGB в HSB, а затем обратно это кажется довольно излишним.

Я мог бы использовать таблицу поиска или предварительно рассчитать цвета в моем приложении, но мне действительно любопытно, есть ли способ заставить мой код работать. Спасибо!

31
Anthony Mattox

Редактировать для каждого комментария изменено «все» на «может быть линейно аппроксимировано».
Правка 2 добавление смещений.


По сути, шаги, которые вы хотите

RBG->HSV->Update hue->RGB

Поскольку эти могут быть аппроксимированы линейными матричными преобразованиями (т.е. они являются ассоциативными), вы можете выполнить это за один шаг без какого-либо неприятного преобразования или потери точности. Вы просто умножаете матрицы преобразования друг на друга и используете это для преобразования ваших цветов.

Здесь есть быстрый шаг за шагом http://beesbuzz.biz/code/hsv_color_transforms.php

Вот код C++ (с удаленными преобразованиями насыщенности и значений):

Color TransformH(
    const Color &in,  // color to transform
    float H
)
{
  float U = cos(H*M_PI/180);
  float W = sin(H*M_PI/180);

  Color ret;
  ret.r = (.299+.701*U+.168*W)*in.r
    + (.587-.587*U+.330*W)*in.g
    + (.114-.114*U-.497*W)*in.b;
  ret.g = (.299-.299*U-.328*W)*in.r
    + (.587+.413*U+.035*W)*in.g
    + (.114-.114*U+.292*W)*in.b;
  ret.b = (.299-.3*U+1.25*W)*in.r
    + (.587-.588*U-1.05*W)*in.g
    + (.114+.886*U-.203*W)*in.b;
  return ret;
}
15
Jacob Eggers

Цветовое пространство RGB описывает куб. Возможно вращение этого куба вокруг диагональной оси от (0,0,0) до (255,255,255) для изменения оттенка. Обратите внимание, что некоторые результаты будут находиться за пределами диапазона от 0 до 255, и их необходимо будет обрезать.

Я наконец-то получил шанс закодировать этот алгоритм. Это на Python, но его должно быть легко перевести на язык по вашему выбору. Формула для трехмерного вращения получена из http://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle

Edit: Если вы видели код, который я разместил ранее, пожалуйста, игнорируйте его. Я так хотел найти формулу для вращения, что я преобразовал решение на основе матрицы в формулу, не осознавая, что матрица была лучшей формой с самого начала. Я все еще упростил вычисление матрицы, используя константу sqrt (1/3) для значений вектора единицы оси, но это намного ближе по духу к эталону и проще в расчете на пиксель apply.

from math import sqrt,cos,sin,radians

def clamp(v):
    if v < 0:
        return 0
    if v > 255:
        return 255
    return int(v + 0.5)

class RGBRotate(object):
    def __init__(self):
        self.matrix = [[1,0,0],[0,1,0],[0,0,1]]

    def set_hue_rotation(self, degrees):
        cosA = cos(radians(degrees))
        sinA = sin(radians(degrees))
        self.matrix[0][0] = cosA + (1.0 - cosA) / 3.0
        self.matrix[0][1] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
        self.matrix[0][2] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
        self.matrix[1][0] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
        self.matrix[1][1] = cosA + 1./3.*(1.0 - cosA)
        self.matrix[1][2] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
        self.matrix[2][0] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
        self.matrix[2][1] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
        self.matrix[2][2] = cosA + 1./3. * (1.0 - cosA)

    def apply(self, r, g, b):
        rx = r * self.matrix[0][0] + g * self.matrix[0][1] + b * self.matrix[0][2]
        gx = r * self.matrix[1][0] + g * self.matrix[1][1] + b * self.matrix[1][2]
        bx = r * self.matrix[2][0] + g * self.matrix[2][1] + b * self.matrix[2][2]
        return clamp(rx), clamp(gx), clamp(bx)

Вот некоторые результаты из вышеперечисленного:

Hue rotation example

Вы можете найти другую реализацию той же идеи на http://www.graficaobscura.com/matrix/index.html

36
Mark Ransom

Я был разочарован большинством ответов, которые я нашел здесь, некоторые были ошибочными и в основном были ошибочными. Я потратил 3 часа, пытаясь понять это. Ответ Марка Рэнсома верен, но я хочу предложить полное решение C, которое также проверено с помощью MATLAB. Я проверил это полностью, и вот код C:

#include <math.h>
typedef unsigned char BYTE; //define an "integer" that only stores 0-255 value

typedef struct _CRGB //Define a struct to store the 3 color values
{
    BYTE r;
    BYTE g;
    BYTE b;
}CRGB;

BYTE clamp(float v) //define a function to bound and round the input float value to 0-255
{
    if (v < 0)
        return 0;
    if (v > 255)
        return 255;
    return (BYTE)v;
}

CRGB TransformH(const CRGB &in, const float fHue)
{
    CRGB out;
    const float cosA = cos(fHue*3.14159265f/180); //convert degrees to radians
    const float sinA = sin(fHue*3.14159265f/180); //convert degrees to radians
    //calculate the rotation matrix, only depends on Hue
    float matrix[3][3] = {{cosA + (1.0f - cosA) / 3.0f, 1.0f/3.0f * (1.0f - cosA) - sqrtf(1.0f/3.0f) * sinA, 1.0f/3.0f * (1.0f - cosA) + sqrtf(1.0f/3.0f) * sinA},
        {1.0f/3.0f * (1.0f - cosA) + sqrtf(1.0f/3.0f) * sinA, cosA + 1.0f/3.0f*(1.0f - cosA), 1.0f/3.0f * (1.0f - cosA) - sqrtf(1.0f/3.0f) * sinA},
        {1.0f/3.0f * (1.0f - cosA) - sqrtf(1.0f/3.0f) * sinA, 1.0f/3.0f * (1.0f - cosA) + sqrtf(1.0f/3.0f) * sinA, cosA + 1.0f/3.0f * (1.0f - cosA)}};
    //Use the rotation matrix to convert the RGB directly
    out.r = clamp(in.r*matrix[0][0] + in.g*matrix[0][1] + in.b*matrix[0][2]);
    out.g = clamp(in.r*matrix[1][0] + in.g*matrix[1][1] + in.b*matrix[1][2]);
    out.b = clamp(in.r*matrix[2][0] + in.g*matrix[2][1] + in.b*matrix[2][2]);
    return out;
}

ПРИМЕЧАНИЕ. Матрица вращения зависит только от оттенка (fHue), поэтому после вычисления matrix[3][3] вы можете повторно использовать его для каждого пикселя изображения, которое подвергается одинаковому преобразованию оттенка! Это резко повысит эффективность Вот код MATLAB, который проверяет результаты:

function out = TransformH(r,g,b,H)
    cosA = cos(H * pi/180);
    sinA = sin(H * pi/180);

    matrix = [cosA + (1-cosA)/3, 1/3 * (1 - cosA) - sqrt(1/3) * sinA, 1/3 * (1 - cosA) + sqrt(1/3) * sinA;
          1/3 * (1 - cosA) + sqrt(1/3) * sinA, cosA + 1/3*(1 - cosA), 1/3 * (1 - cosA) - sqrt(1/3) * sinA;
          1/3 * (1 - cosA) - sqrt(1/3) * sinA, 1/3 * (1 - cosA) + sqrt(1/3) * sinA, cosA + 1/3 * (1 - cosA)];

    in = [r, g, b]';
    out = round(matrix*in);
end

Вот пример ввода/вывода, который воспроизводился обоими кодами:

TransformH(86,52,30,210)
ans =
    36
    43
    88

Таким образом, входной RGB [86,52,30] был преобразован в [36,43,88] с оттенком 210.

6
MasterHD

В основном есть два варианта:

  1. Конвертировать RGB -> HSV, изменить оттенок, конвертировать HSV -> RGB
  2. Изменить оттенок напрямую с помощью линейного преобразования

Я не совсем уверен, как реализовать 2, но в основном вам придется создать матрицу преобразования и отфильтровать изображение через эту матрицу. Однако это изменит цвет изображения вместо изменения только оттенка. Если это хорошо для вас, то это может быть вариант, но если нет, то избежать конверсии нельзя.

Правка

Небольшое исследование показывает это , что подтверждает мои мысли. Подводя итог: преобразование из RGB в HSV должно быть предпочтительным, если требуется точный результат. Изменение исходного изображения RGB с помощью линейного преобразования также приводит к результату, но это скорее окрашивает изображение. Разница объясняется следующим образом: преобразование из RGB в HSV является нелинейным, тогда как преобразование является линейным.

3
Sebastian Dressler

Сообщение старое, и оригинальный постер искал код ios - однако меня отправили сюда через поиск кода для базовых визуальных элементов, поэтому для всех таких, как я, я преобразовал код Марка в модуль vb .net:

Public Module HueAndTry    
    Public Function ClampIt(ByVal v As Double) As Integer    
        Return CInt(Math.Max(0F, Math.Min(v + 0.5, 255.0F)))    
    End Function    
    Public Function DegreesToRadians(ByVal degrees As Double) As Double    
        Return degrees * Math.PI / 180    
    End Function    
    Public Function RadiansToDegrees(ByVal radians As Double) As Double    
        Return radians * 180 / Math.PI    
    End Function    
    Public Sub HueConvert(ByRef rgb() As Integer, ByVal degrees As Double)
        Dim selfMatrix(,) As Double = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}
        Dim cosA As Double = Math.Cos(DegreesToRadians(degrees))
        Dim sinA As Double = Math.Sin(DegreesToRadians(degrees))
        Dim sqrtOneThirdTimesSin As Double = Math.Sqrt(1.0 / 3.0) * sinA
        Dim oneThirdTimesOneSubCos As Double = 1.0 / 3.0 * (1.0 - cosA)
        selfMatrix(0, 0) = cosA + (1.0 - cosA) / 3.0
        selfMatrix(0, 1) = oneThirdTimesOneSubCos - sqrtOneThirdTimesSin
        selfMatrix(0, 2) = oneThirdTimesOneSubCos + sqrtOneThirdTimesSin
        selfMatrix(1, 0) = selfMatrix(0, 2)
        selfMatrix(1, 1) = cosA + oneThirdTimesOneSubCos
        selfMatrix(1, 2) = selfMatrix(0, 1)
        selfMatrix(2, 0) = selfMatrix(0, 1)
        selfMatrix(2, 1) = selfMatrix(0, 2)
        selfMatrix(2, 2) = cosA + oneThirdTimesOneSubCos
        Dim rx As Double = rgb(0) * selfMatrix(0, 0) + rgb(1) * selfMatrix(0, 1) + rgb(2) * selfMatrix(0, 2)
        Dim gx As Double = rgb(0) * selfMatrix(1, 0) + rgb(1) * selfMatrix(1, 1) + rgb(2) * selfMatrix(1, 2)
        Dim bx As Double = rgb(0) * selfMatrix(2, 0) + rgb(1) * selfMatrix(2, 1) + rgb(2) * selfMatrix(2, 2)
        rgb(0) = ClampIt(rx)
        rgb(1) = ClampIt(gx)
        rgb(2) = ClampIt(bx)
    End Sub
End Module

Я помещаю общие термины в (длинные) переменные, но в противном случае это простое преобразование - отлично работало для моих нужд.

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

2
Dave P.

Реализация Javascript (на основе PHP Владимира выше)

const deg = Math.PI / 180;

function rotateRGBHue(r, g, b, hue) {
  const cosA = Math.cos(hue * deg);
  const sinA = Math.sin(hue * deg);
  const neo = [
    cosA + (1 - cosA) / 3,
    (1 - cosA) / 3 - Math.sqrt(1 / 3) * sinA,
    (1 - cosA) / 3 + Math.sqrt(1 / 3) * sinA,
  ];
  const result = [
    r * neo[0] + g * neo[1] + b * neo[2],
    r * neo[2] + g * neo[0] + b * neo[1],
    r * neo[1] + g * neo[2] + b * neo[0],
  ];
  return result.map(x => uint8(x));
}

function uint8(value) {
  return 0 > value ? 0 : (255 < value ? 255 : Math.round(value));
}
1
Matt Blackstone

Кажется, переход на HSV имеет больше смысла. Sass предоставляет несколько удивительных помощников цвета. Это в Ruby, но это может быть полезным. 

http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html

0
Scott Messinger

Скотт .... не совсем. Кажется, что алгоритм работает так же, как в HSL/HSV, но быстрее .. Кроме того, если вы просто умножаете первые 3 элемента массива на коэффициент для серого, вы добавляете/уменьшаете яркость.

Пример ... Оттенки серого из Rec709 имеют эти значения [GrayRedFactor_Rec709: R $ 0,212671 GrayGreenFactor_Rec709: R $ 0,715160 GrayBlueFactor_Rec709: R $ 0,072169]

Когда вы умножаете Self.matrix [x] [x] с корреспондентом GreyFactor, вы уменьшаете яркость, не затрагивая насыщенность Пример:

def set_hue_rotation(self, degrees):
    cosA = cos(radians(degrees))
    sinA = sin(radians(degrees))
    self.matrix[0][0] = (cosA + (1.0 - cosA) / 3.0) * 0.212671
    self.matrix[0][1] = (1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA) * 0.715160
    self.matrix[0][2] = (1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA) * 0.072169
    self.matrix[1][0] = self.matrix[0][2] <---Not sure, if this is the right code, but i think you got the idea
    self.matrix[1][1] = self.matrix[0][0]
    self.matrix[1][2] = self.matrix[0][1]

И наоборот также. Если вы делите вместо умножения, яркость резко увеличивается.

Из того, что я тестирую, эти алгоритмы могут быть прекрасной заменой HSL, если, конечно, вам не нужно насыщение.

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

0
Gustavo Trigueiros

Для тех, кому необходим описанный выше (без гамма-коррекции) сдвиг оттенка в качестве параметризованного пиксельного шейдера HLSL (я прошел через это вместе для приложения WPF и подумал, что я мог бы даже просто поделиться им):

    sampler2D implicitInput : register(s0);
    float factor : register(c0);

    float4 main(float2 uv : TEXCOORD) : COLOR
    {
            float4 color = tex2D(implicitInput, uv);

            float h = 360 * factor;          //Hue
            float s = 1;                     //Saturation
            float v = 1;                     //Value
            float M_PI = 3.14159265359;

            float vsu = v * s*cos(h*M_PI / 180);
            float vsw = v * s*sin(h*M_PI / 180);

            float4 result;
            result.r = (.299*v + .701*vsu + .168*vsw)*color.r
                            + (.587*v - .587*vsu + .330*vsw)*color.g
                            + (.114*v - .114*vsu - .497*vsw)*color.b;
            result.g = (.299*v - .299*vsu - .328*vsw)*color.r
                            + (.587*v + .413*vsu + .035*vsw)*color.g
                            + (.114*v - .114*vsu + .292*vsw)*color.b;
            result.b = (.299*v - .300*vsu + 1.25*vsw)*color.r
                            + (.587*v - .588*vsu - 1.05*vsw)*color.g
                            + (.114*v + .886*vsu - .203*vsw)*color.b;;
            result.a = color.a;

            return result;
    }
0
Robin B

Отличный код, но мне интересно, что это может быть быстрее, если вы просто не используете self.matrix [2] [0], self.matrix [2] [1], self.matrix [2] [1]

Следовательно, set_hue_rotation можно записать просто как:

def set_hue_rotation(self, degrees):
    cosA = cos(radians(degrees))
    sinA = sin(radians(degrees))
    self.matrix[0][0] = cosA + (1.0 - cosA) / 3.0
    self.matrix[0][1] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
    self.matrix[0][2] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
    self.matrix[1][0] = self.matrix[0][2] <---Not sure, if this is the right code, but i think you got the idea
    self.matrix[1][1] = self.matrix[0][0]
    self.matrix[1][2] = self.matrix[0][1]
0
Gustavo Trigueiros

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

Например, если повернуть оттенок на 180º с использованием цветового пространства HSV, изображение может привести к красноватому оттенку.

Но по алгоритму Марка изображение правильно вращается. Например, тоны скинов (Hue = 17, Sat = 170, L = 160 в PSP) превращаются в синий цвет с оттенком около 144 в PSP, а все остальные цвета изображения правильно вращаются.

Алгоритм имеет смысл, поскольку Хюэ - это не более, чем логарифмическая функция арктана красного, зеленого и синего цветов, как определено этой формулой:

Hue = arctan((logR-logG)/(logR-logG+2*LogB))
0
Gustavo Trigueiros

Реализация PHP:

class Hue
{
    public function convert(int $r, int $g, int $b, int $hue)
    {
        $cosA = cos($hue * pi() / 180);
        $sinA = sin($hue * pi() / 180);

        $neo = [
            $cosA + (1 - $cosA) / 3,
            (1 - $cosA) / 3 - sqrt(1 / 3) * $sinA,
            (1 - $cosA) / 3 + sqrt(1 / 3) * $sinA,
        ];

        $result = [
            $r * $neo[0] + $g * $neo[1] + $b * $neo[2],
            $r * $neo[2] + $g * $neo[0] + $b * $neo[1],
            $r * $neo[1] + $g * $neo[2] + $b * $neo[0],
        ];

        return array_map([$this, 'crop'], $result);
    }

    private function crop(float $value)
    {
        return 0 > $value ? 0 : (255 < $value ? 255 : (int)round($value));
    }
}
0
Vladimir Klubov