转换RGB颜色的色调

问题描述 投票:33回答:13

我正在尝试编写一个函数来移动RGB颜色的色调。具体来说,我在iOS应用程序中使用它,但数学是通用的。

下图显示了R,G和B值如何相对于色调变化。

看一下这似乎是一个相对简单的编写一个函数来转移色调而不做任何讨厌的转换到不同的颜色格式会引入更多的错误(如果继续对颜色进行小的移位可能会出现问题) ,我怀疑计算成本会更高。

这是我到目前为止所做的工作。如果你从纯黄色或青色或洋红色转移,它会完美地工作,但在某些地方会变得有点眩晕。

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然后再回来之间看起来相当矫枉过正。

我可能最终使用查找表或预先计算我的应用程序中的颜色,但我真的好奇是否有办法使我的代码工作。谢谢!

objective-c c colors rgb
13个回答
16
投票

每条评论的编辑更改为“全部”到“可以线性近似”。 编辑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;
}

0
投票

斯科特......不完全是。算法似乎与HSL / HSV相同,但速度更快。此外,如果您只是将数组的前3个元素与灰色系数相乘,则可以添加/减少亮度。

示例...来自Rec709的灰度值具有这些值[GrayRedFactor_Rec709:R $ 0.212671 GrayGreenFactor_Rec709:R $ 0.715160 GrayBlueFactor_Rec709:R $ 0.072169]

当你将self.matrix [x] [x]与红色因子对应物相乘时,你会减少亮度而不会触及饱和度Ex:

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
投票

此外,马克的算法产生更准确的结果。

例如,如果使用HSV色彩空间将色调旋转到180º,图像可能会产生偏红的色调。

但是在Mark的算法中,图像正确旋转。例如,皮肤色调(Pue中的Hue = 17,Sat = 170,L = 160)适当地变为蓝色,在PSP中具有144左右的Hue,并且图像的所有其他颜色都正确旋转。

算法是有道理的,因为Hue只不过是这个公式所定义的红色,绿色,蓝色的arctan的对数函数:

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

0
投票

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
投票

对于任何需要上述(伽玛未校正的)色调偏移作为参数化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
投票

在glsl上最紧凑的版本,我能够做到:

vec3 hs(vec3 c, float s){
    vec3 m=vec3(cos(s),s=sin(s)*.5774,-s);
    return c*mat3(m+=(1.-m.x)/3.,m.zxy,m.yzx);
}

39
投票

RGB颜色空间描述了一个立方体。可以围绕对角轴旋转此立方体(0,0,0)到(255,255,255)以实现色调的变化。请注意,某些结果将位于0到255范围之外,需要进行裁剪。

我终于有机会编写了这个算法。它在Python中,但应该很容易翻译成您选择的语言。 3D旋转的公式来自http://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle

编辑:如果您看到我之前发布的代码,请忽略它。我非常渴望找到一个旋转公式,我将基于矩阵的解决方案转换为公式,没有意识到矩阵一直是最好的形式。我仍然使用轴单位矢量值的常数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)

以下是上述结果:

您可以在http://www.graficaobscura.com/matrix/index.html找到相同想法的不同实现


7
投票

我对这里找到的大多数答案感到失望,有些是有缺陷的,基本上是错误的。我最终花了3个多小时试图解决这个问题。 Mark Ransom的答案是正确的,但我想提供一个完整的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

所以使用[86,52,30]的色调将[36,43,88]的输入RGB转换为210


3
投票

基本上有两种选择:

  1. 转换RGB - > HSV,改变色调,转换HSV - > RGB
  2. 直接使用线性变换更改色调

我不太确定如何实现2,但基本上你必须创建一个变换矩阵并通过这个矩阵过滤图像。但是,这将重新着色图像而不是仅改变色调。如果这对您来说没问题,那么这可能是一个选项,但如果不是,则无法避免转换。

编辑

一个小小的研究显示this,这证实了我的想法。总结:如果需要精确的结果,则应首选从RGB到HSV的转换。通过线性变换修改原始RGB图像也会产生结果,但这会使图像着色。差异解释如下:从RGB到HSV的转换是非线性的,而转换是线性的。


2
投票

帖子很旧,原来的海报正在寻找ios代码 - 但是,我是通过搜索视觉基本代码发送到这里的,所以对于像我这样的所有人,我将Mark的代码转换为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

我将常用术语放入(长)变量中,但除此之外它是一个简单的转换 - 适合我的需要。

顺便说一下,我试图让Mark为他的优秀代码留下一个赞成,但我自己没有足够的选票让它可见(提示,提示)。


2
投票

Javascript实现(基于Vladimir的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
投票

似乎转换为HSV最有意义。 Sass提供了一些令人惊叹的颜色助手。它是红宝石,但它可能提供有用的。

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


1
投票

WebGL版本:

vec3 hueShift(vec3 col, float shift){
    vec3 m = vec3(cos(shift), -sin(shift) * .57735, 0);
    m = vec3(m.xy, -m.y) + (1. - m.x) * .33333;
    return mat3(m, m.zxy, m.yzx) * col;
}

0
投票

优秀的代码,但是,我想知道如果你不使用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]
© www.soinside.com 2019 - 2024. All rights reserved.