如何更改图像中的特定颜色?

时间:2011-11-08 06:18:33

标签: iphone objective-c image-processing core-graphics pixel

我的问题是,如果我有狮子图像,我只想改变狮子的颜色而不是背景颜色。为此我提到了这个SO question,但它改变了整个图像的颜色。此外,图像看起来不太好。我需要像photoshop那样的颜色变化。是否可以在coregraphics中执行此操作,或者我必须使用任何其他库。

编辑:我需要将颜色更改为iQuikColor app

enter image description here

5 个答案:

答案 0 :(得分:34)

这花了很长时间才弄明白,主要是因为我想使用Core Image和CIColorCube在Swift中运行它。

@Miguel的解释是关于你需要用另一个“Hue角度范围”替换“Hue角度范围”的方法。您可以阅读上面的帖子,了解有关色调角度范围的详细信息。

我做了一个快速的应用程序,用你在Hue滑块上选择的任何内容替换下面的默认蓝色卡车。

enter image description here

您可以滑动滑块告诉应用程序您要用蓝色替换蓝色的颜色。

我将Hue范围硬编码为60度,这通常似乎包含大部分特定颜色,但如果需要,您可以编辑它。

enter image description here

enter image description here

请注意,它没有为轮胎或尾灯着色,因为它超出了卡车默认蓝色调的60度范围,但它确实适当地处理了阴影。

首先,您需要将RGB转换为HSV(Hue值)的代码:

func RGBtoHSV(r : Float, g : Float, b : Float) -> (h : Float, s : Float, v : Float) {
    var h : CGFloat = 0
    var s : CGFloat = 0
    var v : CGFloat = 0
    let col = UIColor(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: 1.0)
    col.getHue(&h, saturation: &s, brightness: &v, alpha: nil)
    return (Float(h), Float(s), Float(v))
}

然后您需要将HSV转换为RGB。当您发现处于所需色调范围的色调(也就是默认卡车的蓝色色调)时,您希望使用此功能来保存您所做的任何调整。

func HSVtoRGB(h : Float, s : Float, v : Float) -> (r : Float, g : Float, b : Float) {
    var r : Float = 0
    var g : Float = 0
    var b : Float = 0
    let C = s * v
    let HS = h * 6.0
    let X = C * (1.0 - fabsf(fmodf(HS, 2.0) - 1.0))
    if (HS >= 0 && HS < 1) {
        r = C
        g = X
        b = 0
    } else if (HS >= 1 && HS < 2) {
        r = X
        g = C
        b = 0
    } else if (HS >= 2 && HS < 3) {
        r = 0
        g = C
        b = X
    } else if (HS >= 3 && HS < 4) {
        r = 0
        g = X
        b = C
    } else if (HS >= 4 && HS < 5) {
        r = X
        g = 0
        b = C
    } else if (HS >= 5 && HS < 6) {
        r = C
        g = 0
        b = X
    }
    let m = v - C
    r += m
    g += m
    b += m
    return (r, g, b)
}

现在,您只需循环浏览完整的RGBA颜色立方体,并“调整”“默认蓝色”色调范围内的任何颜色与新所需色调的颜色。然后使用Core Image和CIColorCube过滤器将调整后的颜色立方体应用于图像。

func render() {
    let centerHueAngle: Float = 214.0/360.0 //default color of truck body blue
    let destCenterHueAngle: Float = slider.value
    let minHueAngle: Float = (214.0 - 60.0/2.0) / 360 //60 degree range = +30 -30
    let maxHueAngle: Float = (214.0 + 60.0/2.0) / 360
    var hueAdjustment = centerHueAngle - destCenterHueAngle
    let size = 64
    var cubeData = [Float](count: size * size * size * 4, repeatedValue: 0)
    var rgb: [Float] = [0, 0, 0]
    var hsv: (h : Float, s : Float, v : Float)
    var newRGB: (r : Float, g : Float, b : Float)
    var offset = 0
    for var z = 0; z < size; z++ {
        rgb[2] = Float(z) / Float(size) // blue value
        for var y = 0; y < size; y++ {
            rgb[1] = Float(y) / Float(size) // green value
            for var x = 0; x < size; x++ {
                rgb[0] = Float(x) / Float(size) // red value
                hsv = RGBtoHSV(rgb[0], g: rgb[1], b: rgb[2])
                if hsv.h < minHueAngle || hsv.h > maxHueAngle {
                    newRGB.r = rgb[0]
                    newRGB.g = rgb[1]
                    newRGB.b = rgb[2]
                } else {
                    hsv.h = destCenterHueAngle == 1 ? 0 : hsv.h - hueAdjustment //force red if slider angle is 360
                    newRGB = HSVtoRGB(hsv.h, s:hsv.s, v:hsv.v)
                }
                cubeData[offset]   = newRGB.r
                cubeData[offset+1] = newRGB.g
                cubeData[offset+2] = newRGB.b
                cubeData[offset+3] = 1.0
                offset += 4
            }
        }
    }
    let data = NSData(bytes: cubeData, length: cubeData.count * sizeof(Float))
    let colorCube = CIFilter(name: "CIColorCube")!
    colorCube.setValue(size, forKey: "inputCubeDimension")
    colorCube.setValue(data, forKey: "inputCubeData")
    colorCube.setValue(ciImage, forKey: kCIInputImageKey)
    if let outImage = colorCube.outputImage {
        let context = CIContext(options: nil)
        let outputImageRef = context.createCGImage(outImage, fromRect: outImage.extent)
        imageView.image = UIImage(CGImage: outputImageRef)
    }
}

您可以下载sample project here

答案 1 :(得分:21)

请参阅下面的答案。我没有提供完整的解决方案。


以下是使用OpenCV的可能解决方案草图:

  • 使用cvCvtColor将图像从RGB转换为HSV(我们只想更改色调)。
  • 使用cvThreshold指定一定的容差来隔离颜色(您想要一系列颜色,而不是一种颜色)。
  • 使用blob检测库(如cvBlobsLib)丢弃低于最小尺寸的颜色区域。这将消除场景中相似颜色的点。
  • 使用cvInRangeS屏蔽颜色,并使用生成的蒙版应用新色调。
  • cvMerge新图像的新色调,图像由您在第一步中保存的饱和度和亮度通道组成。

网上有几个OpenCV iOS端口,例如:http://www.eosgarden.com/en/opensource/opencv-ios/overview/我自己没有尝试过,但似乎是一个很好的研究方向。

答案 2 :(得分:13)

我将假设您知道如何执行这些基本操作,因此这些操作不会包含在我的解决方案中:

  • 加载图片
  • 获取已加载图像的给定像素的RGB值
  • 设置给定像素的RGB值
  • 显示已加载的图像,和/或将其保存回磁盘。

首先,让我们考虑一下如何描述源颜色和目标颜色。显然,您无法将这些指定为精确的RGB值,因为照片的颜色会有轻微变化。例如,您张贴的卡车图片中的绿色像素并非完全相同的绿色阴影。 RGB颜色模型在表达基本颜色特征方面不是很好,因此如果将像素转换为HSL,您将获得更好的结果。 Here是用于将RGB转换为HSL并返回的C函数。

HSL颜色模型描述了颜色的三个方面:

  1. Hue - 主要的感知颜色 - 即红色,绿色,橙色等
  2. 饱和度 - 颜色的“完整”程度 - 即从全彩到无颜色
  3. 亮度 - 颜色有多亮
  4. 因此,例如,如果要查找图片中的所有绿色像素,则将每个像素从RGB转换为HSL,然后查找与绿色对应的H值,并对“近绿色”颜色具有一定的容差。以下是来自维基百科的Hue图表:

    因此,在您的情况下,您将看到色调为120度+/-一些量的像素。范围越大,选择的颜色越多。如果您的范围太宽,您将开始看到黄色和青色像素被选中,因此您必须找到正确的范围,您甚至可能希望为应用程序控件的用户提供选择此范围。

    除了通过Hue选择之外,您可能还想允许饱和度和亮度范围,以便您可以选择对要选择用于着色的像素设置更多限制。

    最后,您可能希望为用户提供绘制“套索选择”的功能,以便可以将图片的特定部分排除在着色之外。这就是你如何告诉应用程序你想要绿色卡车的车身,而不是绿色车轮。

    一旦你知道你想要修改哪些像素就可以改变它们的颜色了。

    对像素进行着色的最简单方法是仅更改色相,使原始像素保持饱和度和亮度。因此,例如,如果您想要制作绿色像素洋红色,您将为所选像素的所有Hue值添加180度(确保使用模数360数学)。

    如果您想要更复杂,您还可以对饱和度应用更改,这将为您提供更广泛的音调。我认为Lightness最好不要单独使用,你可以进行小的调整,图像看起来仍然很好,但如果你离原版太远,你可能会开始看到过程像素与背景像素边界的硬边。

    获得彩色化HSL像素后,只需将其转换回RGB并将其写回图像。

    我希望这会有所帮助。我应该做的最后一个评论是代码中的Hue值通常记录在0-255范围内,但是许多应用程序将它们显示为范围为0到360度的色轮。牢记这一点!

答案 3 :(得分:1)

我可以建议您使用OpenCV吗?它是一个开源图像处理库,它也有一个iOS端口。有很多关于如何使用它并进行设置的博客文章。

它有一大堆功能可以帮助你做好你正在尝试的事情。你可以只使用CoreGraphics来做,但最终结果看起来不会像OpenCV那样好。

它是由麻省理工学院的一些人开发的,所以你可能期望它在边缘检测和对象跟踪等方面做得很好。我记得读过一篇关于如何用OpenCV将某种颜色与图片分开的博客 - 这些例子显示了非常好的结果。有关示例,请参阅here。从那里我无法想象实际上将分离的颜色改变为其他东西将是一项巨大的工作。

答案 4 :(得分:0)

我不知道CoreGraphics操作,我没有看到合适的CoreImage过滤器。如果这是正确的,那么这是一个正确的方向:

假设您有CGImage(或uiImage.CGImage):

  • 首先创建一个新的CGBitmapContext
  • 将源图像绘制到位图上下文
  • 获取位图像素数据的句柄

了解缓冲区的结构,以便正确填充具有以下形式的像素值的二维数组:

typedef struct t_pixel {
  uint8_t r, g, b, a;
} t_pixel;

然后创建要定位的颜色:

const t_pixel ColorToLocate = { 0,0,0,255 }; // << black, opaque

及其替代值:

const t_pixel SubstitutionColor = { 255,255,255,255 }; // << white, opaque
  • 迭代位图上下文的像素缓冲区,创建t_pixel s。
  • 当您找到与ColorToLocate匹配的像素时,请使用SubstitutionColor中的值替换源值。

  • CGImage创建新的CGBitmapContext

这很容易!所有这一切都需要CGImage,替换完全颜色匹配,并生成新的CGImage

你想要的更复杂。对于此任务,您需要一个良好的边缘检测算法。

我没有使用过此链接的应用。如果它仅限于几种颜色,那么它们可能只是交换通道值,与边缘检测配对(请记住,缓冲区也可以用多种颜色模型表示 - 而不仅仅是RGBA)。

如果(在你链接的应用中)用户可以选择任意颜色,值和边缘阈值,那么你将不得不使用真正的混合和边缘检测。如果您需要了解如何完成此操作,您可能需要查看一个包,例如Gimp(它是一个打开的图像编辑器) - 它们具有检测边缘的算法并按颜色进行选择。