如何计算UIImage的平均颜色?

Swe*_*per 12 average pixel colors ios swift

我想构建一个应用程序,让用户选择一个图像,它输出"平均颜色".

例如,这张图片:

在此输入图像描述

平均颜色为绿色/淡黄色.

目前,我得到了这段代码:

// In a UIColor extension
public static func fromImage(image: UIImage) -> UIColor {
    var totalR: CGFloat = 0
    var totalG: CGFloat = 0
    var totalB: CGFloat = 0

    var count: CGFloat = 0

    for x in 0..<Int(image.size.width) {
        for y in 0..<Int(image.size.height) {
            count += 1
            var rF: CGFloat = 0,
            gF: CGFloat = 0,
            bF: CGFloat = 0,
            aF: CGFloat = 0
            image.getPixelColor(CGPoint(x: x, y: y)).getRed(&rF, green: &gF, blue: &bF, alpha: &aF)
            totalR += rF
            totalG += gF
            totalB += bF
        }
    }

    let averageR = totalR / count
    let averageG = totalG / count
    let averageB = totalB / count

    return UIColor(red: averageR, green: averageG, blue: averageB, alpha: 1.0)
}
Run Code Online (Sandbox Code Playgroud)

在哪里getPixelColor定义为:

extension UIImage {
    func getPixelColor(pos: CGPoint) -> UIColor {

        let pixelData = CGDataProviderCopyData(CGImageGetDataProvider(self.CGImage))
        let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

        let pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4

        let r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
        let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
        let b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
        let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)

        return UIColor(red: r, green: g, blue: b, alpha: a)
    }
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我在这里做的非常天真:我遍历图像中的所有像素,向上添加RGB,然后除以计数.

当我运行应用程序并选择图像时,应用程序会冻结.我知道这是因为图像太大而且两个嵌套的for循环执行的次数太多了.

我想找到一种有效获取图像平均颜色的方法.我怎么做?

aya*_*aio 11

这不是一个真正的"答案",但我觉得我可以提供一些关于颜色检测的提示,以及它的价值,让我们走吧.

调整

在您的情况下,速度的最大技巧是将图像调整为合理尺寸的正方形.

没有神奇的价值,因为它取决于图像是否有噪声,等等,但是以小于300x300为目标的采样方法似乎是可以接受的,例如(尽管不要过于极端).

使用快速调整大小的方法 - 无需保持比率,抗锯齿或任何东西(SO上有许多可用的实现).我们正在计算颜色,我们对图像显示的方面不感兴趣.

我们从调整大小获得的速度增益非常值得在调整大小时丢失的几个周期.

步进

第二个技巧是通过步进进行采样.

对于大多数照片,您可以负担每隔一个像素或每隔一行的样本,并保持相同的颜色检测精度.

您也不能在几个像素宽的范围内对大多数照片的边框进行采样(或丢弃一次采样) - 因为边框,框架,晕影等等.它有助于制作平均值(您希望丢弃所有太过边缘且可能偏向的)结果不必要).

过滤掉噪音

为了在采样中非常精确,你必须丢弃噪音:如果你保留所有的灰色,所有的检测都会太灰.例如,通过不保持具有非常低饱和度的颜色来滤除灰色.

计算颜色的出现次数

然后你可以计算你的颜色,你应该使用独特的颜色.例如,使用NSCountedSet来存储颜色及其出现次数,然后您可以处理每种颜色的出现次数并了解最常见的颜色等.

最后提示:在计算平均值之前滤除孤独的颜色 - 您决定阈值(例如"如果它在300x300图像中显示的次数少于N次则不值得使用").提高准确性.


小智 8

你需要使用Accelerate Library,Apple有一本带有一些示例代码的手册,它可以在Swift或ObjC中使用

这是一个让你前进的样本,我用它来计算一个人的心率和心率变异性,使用手指在相机镜头上的颜色变化.

完整代码:https: //github.com/timestocome/SwiftHeartRate/blob/master/Swift%20Pulse%20Reader/ViewController.swift

这是Swift的旧版本,但我想你会明白这个想法.我是以240 fps的速度完成此操作,但是图像的裁剪部分较小.

相关代码在这里:

// compute the brightness for reg, green, blue and total
    // pull out color values from pixels ---  image is BGRA
    var greenVector:[Float] = Array(count: numberOfPixels, repeatedValue: 0.0)
    var blueVector:[Float] = Array(count: numberOfPixels, repeatedValue: 0.0)
    var redVector:[Float] = Array(count: numberOfPixels, repeatedValue: 0.0)

    vDSP_vfltu8(dataBuffer, 4, &blueVector, 1, vDSP_Length(numberOfPixels))
    vDSP_vfltu8(dataBuffer+1, 4, &greenVector, 1, vDSP_Length(numberOfPixels))
    vDSP_vfltu8(dataBuffer+2, 4, &redVector, 1, vDSP_Length(numberOfPixels))



    // compute average per color
    var redAverage:Float = 0.0
    var blueAverage:Float = 0.0
    var greenAverage:Float = 0.0

    vDSP_meamgv(&redVector, 1, &redAverage, vDSP_Length(numberOfPixels))
    vDSP_meamgv(&greenVector, 1, &greenAverage, vDSP_Length(numberOfPixels))
    vDSP_meamgv(&blueVector, 1, &blueAverage, vDSP_Length(numberOfPixels))



    // convert to HSV ( hue, saturation, value )
    // this gives faster, more accurate answer
    var hue: CGFloat = 0.0
    var saturation: CGFloat = 0.0
    var brightness: CGFloat = 0.0
    var alpha: CGFloat = 1.0

    var color: UIColor = UIColor(red: CGFloat(redAverage/255.0), green: CGFloat(greenAverage/255.0), blue: CGFloat(blueAverage/255.0), alpha: alpha)
    color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)




    // 5 count rolling average
    let currentHueAverage = hue/movingAverageCount
    movingAverageArray.removeAtIndex(0)
    movingAverageArray.append(currentHueAverage)

    let movingAverage = movingAverageArray[0] + movingAverageArray[1] + movingAverageArray[2] + movingAverageArray[3] + movingAverageArray[4]
Run Code Online (Sandbox Code Playgroud)


小智 5

斯威夫特 3 版本

extension UIImage {

 func averageColor(alpha : CGFloat) -> UIColor {

    let rawImageRef : CGImage = self.cgImage!
    let  data : CFData = rawImageRef.dataProvider!.data!
    let rawPixelData  =  CFDataGetBytePtr(data);

    let imageHeight = rawImageRef.height
    let imageWidth  = rawImageRef.width
    let bytesPerRow = rawImageRef.bytesPerRow
    let stride = rawImageRef.bitsPerPixel / 6

    var red = 0
    var green = 0
    var blue  = 0




    for row in 0...imageHeight {
        var rowPtr = rawPixelData! + bytesPerRow * row
        for _ in 0...imageWidth {
            red    += Int(rowPtr[0])
            green  += Int(rowPtr[1])
            blue   += Int(rowPtr[2])
            rowPtr += Int(stride)
        }
    }

    let  f : CGFloat = 1.0 / (255.0 * CGFloat(imageWidth) * CGFloat(imageHeight))
    return UIColor(red: f * CGFloat(red), green: f * CGFloat(green), blue: f * CGFloat(blue) , alpha: alpha)
 }
}
Run Code Online (Sandbox Code Playgroud)