如何计算UIImage的平均颜色?

时间:2016-04-23 00:50:51

标签: ios swift colors average pixel

我想构建一个应用程序,让用户选择一个图像,然后输出“平均颜色”。

例如,此图片:

enter image description here

平均颜色为浅黄色/黄色。

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

// 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)
}

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)
    }
}

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

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

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

6 个答案:

答案 0 :(得分:10)

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

调整大小

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

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

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

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

步进

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

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

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

过滤掉噪音

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

计算颜色的出现次数

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

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

答案 1 :(得分:8)

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

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

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

它是Swift的旧版本,但我认为你会得到这个想法。我这样做是以240帧/秒的速度拍摄的,但图像的部分被裁剪得很小。

此处的相关代码:

// 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]

答案 2 :(得分:4)

Swift 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)
 }
}

答案 3 :(得分:0)

如果您想要一个确切的结果,我相信无论您做什么,无论是您自己还是通过API,您都会至少经历一次所有像素。

您目前使用已分配的(现有)内存,这意味着至少您不会崩溃:)

如果冻结是问题,那是因为您正在UI线程中执行,并且应该在后台线程中移动所有这些处理,例如使用AsyncTask。

您可以尝试调整图像视图的大小,获取渲染缓冲区并处理该图像,但是您将使用更多内存,我不相信它也会更快。

答案 4 :(得分:0)

我最终得到了这个类扩展:

extension UIImage {

func averageColor(alpha : CGFloat) -> UIColor {

    let rawImageRef : CGImageRef = self.CGImage!
    let  data : CFDataRef = CGDataProviderCopyData(CGImageGetDataProvider(rawImageRef))!
    let rawPixelData  =  CFDataGetBytePtr(data);

    let imageHeight = CGImageGetHeight(rawImageRef)
    let imageWidth  = CGImageGetWidth(rawImageRef)
    let bytesPerRow = CGImageGetBytesPerRow(rawImageRef)
    let stride = CGImageGetBitsPerPixel(rawImageRef) / 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)
}

}

答案 5 :(得分:0)

也许对某人有帮助-我的扩展名返回颜色,但是对于UIView(可以在其中找到您的图片),它使用旧的QuartzCore代码

// ObjC case
@interface UIView (ColorOfPoint)
    - (UIColor *) colorOfPoint:(CGPoint)point withSize:(CGSize)size;
@end

#import <QuartzCore/QuartzCore.h>

@implementation UIView (ColorOfPoint)

- (UIColor *) colorOfPoint:(CGPoint)point withSize:(CGSize)size {
    unsigned char pixel[4] = {0};

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    CGContextRef context = CGBitmapContextCreate(pixel, size.width, size.height, 8, 4, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast);

    CGContextTranslateCTM(context, -point.x-size.width/2, -point.y-size.height/2);

    [self.layer renderInContext:context];

    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);

    //NSLog(@"pixel: %d %d %d %d", pixel[0], pixel[1], pixel[2], pixel[3]);

    UIColor *color = [UIColor colorWithRed:pixel[0]/255.0 green:pixel[1]/255.0 blue:pixel[2]/255.0 alpha:pixel[3]/255.0];

    return color;
}

@end