Swift 2.2 - 统计 UIImage 中黑色像素数量

3

我需要计算UIImage中所有黑色像素的数量。我找到了一段可能能用的代码,但是它是用Objective-C编写的。我尝试将其转换为Swift,但是出现了许多错误,我找不到解决方法。

使用Swift做这件事的最佳方法是什么?

简单图像enter image description here

Objective-C:

/**
 * Structure to keep one pixel in RRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA format
 */

struct pixel {
    unsigned char r, g, b, a;
};

/**
 * Process the image and return the number of pure red pixels in it.
 */

- (NSUInteger) processImage: (UIImage*) image
{
    NSUInteger numberOfRedPixels = 0;

    // Allocate a buffer big enough to hold all the pixels

    struct pixel* pixels = (struct pixel*) calloc(1, image.size.width * image.size.height * sizeof(struct pixel));
    if (pixels != nil)
    {
        // Create a new bitmap

        CGContextRef context = CGBitmapContextCreate(
            (void*) pixels,
            image.size.width,
            image.size.height,
            8,
            image.size.width * 4,
            CGImageGetColorSpace(image.CGImage),
            kCGImageAlphaPremultipliedLast
        );

        if (context != NULL)
        {
            // Draw the image in the bitmap

            CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, image.size.width, image.size.height), image.CGImage);

            // Now that we have the image drawn in our own buffer, we can loop over the pixels to
            // process it. This simple case simply counts all pixels that have a pure red component.

            // There are probably more efficient and interesting ways to do this. But the important
            // part is that the pixels buffer can be read directly.

            NSUInteger numberOfPixels = image.size.width * image.size.height;

            while (numberOfPixels > 0) {
                if (pixels->r == 255) {
                    numberOfRedPixels++;
                }
                pixels++;
                numberOfPixels--;
            }

            CGContextRelease(context);
        }

        free(pixels);
    }

    return numberOfRedPixels;
}
2个回答

10
更快的方法是使用 Accelerate 的 vImageHistogramCalculation 来获取图像中不同通道的直方图:
let img: CGImage = CIImage(image: image!)!.cgImage!

let imgProvider: CGDataProvider = img.dataProvider!
let imgBitmapData: CFData = imgProvider.data!
var imgBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: CFDataGetBytePtr(imgBitmapData)), height: vImagePixelCount(img.height), width: vImagePixelCount(img.width), rowBytes: img.bytesPerRow)

let alpha = [UInt](repeating: 0, count: 256)
let red = [UInt](repeating: 0, count: 256)
let green = [UInt](repeating: 0, count: 256)
let blue = [UInt](repeating: 0, count: 256)

let alphaPtr = UnsafeMutablePointer<vImagePixelCount>(mutating: alpha) as UnsafeMutablePointer<vImagePixelCount>?
let redPtr = UnsafeMutablePointer<vImagePixelCount>(mutating: red) as UnsafeMutablePointer<vImagePixelCount>?
let greenPtr = UnsafeMutablePointer<vImagePixelCount>(mutating: green) as UnsafeMutablePointer<vImagePixelCount>?
let bluePtr = UnsafeMutablePointer<vImagePixelCount>(mutating: blue) as UnsafeMutablePointer<vImagePixelCount>?

let rgba = [redPtr, greenPtr, bluePtr, alphaPtr]

let histogram = UnsafeMutablePointer<UnsafeMutablePointer<vImagePixelCount>?>(mutating: rgba)
let error = vImageHistogramCalculation_ARGB8888(&imgBuffer, histogram, UInt32(kvImageNoFlags))
运行后,alpharedgreenblue现在成为您图像中颜色的直方图。如果redgreenblue每个仅在第0个位置具有计数,而alpha仅在最后一个位置具有计数,则您的图像为黑色。
如果您甚至不想检查多个数组,可以使用vImageMatrixMultiply来组合您的不同通道:
let readableMatrix: [[Int16]] = [
    [3,     0,     0,    0]
    [0,     1,     1,    1],
    [0,     0,     0,    0],
    [0,     0,     0,    0]
]

var matrix: [Int16] = [Int16](repeating: 0, count: 16)

for i in 0...3 {
    for j in 0...3 {
        matrix[(3 - j) * 4 + (3 - i)] = readableMatrix[i][j]
    }
}
vImageMatrixMultiply_ARGB8888(&imgBuffer, &imgBuffer, matrix, 3, nil, nil, UInt32(kvImageNoFlags))

如果您在制作直方图之前执行此操作,imgBuffer将被原地修改以平均每个像素中的RGB,并将平均值写入B通道。因此,您只需检查blue直方图而不是所有三个。

(顺便说一句,我找到的有关vImageMatrixMultiply的最佳描述是在源代码中,比如在https://github.com/phracker/MacOSX-SDKs/blob/2d31dd8bdd670293b59869335d9f1f80ca2075e0/MacOSX10.7.sdk/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/Headers/Transform.h#L21


1
非常好的答案 - 这应该被接受为正确的答案。非常快速和准确,尽管在我的实现中,当 alpha 通道(当它是纯黑色时)只有最后一个值时,它似乎有其唯一的价值。奇怪。 - SomaMan
糟糕,好发现:RGB的最小值是黑色,但alpha的最大值完全可见。我会修复的,谢谢! - Cannoliopsida
1
非常感谢!又快又好。应该是正确的答案。 - yuji
你的帖子是我用来实现直方图函数的依据。谢谢!不过在Swift 5中,我会收到有关UnsafeMutablePointer的警告。我将您的代码与苹果文档中的代码结合起来,并在此处发布了Swift 5版本:https://dev59.com/FJzha4cB1Zd3GeqPE2_V#66369478 - Rethunk

2

我现在遇到了类似的问题,需要确定一张图片是否完全是黑色。下面的代码将返回图像中找到的纯黑色像素数。

如果您想提高阈值,可以更改比较值,并允许它容忍更广泛的可能颜色范围。

import UIKit

extension UIImage {
    var blackPixelCount: Int {
        var count = 0
        for x in 0..<Int(size.width) {
            for y in 0..<Int(size.height) {
                count = count + (isPixelBlack(CGPoint(x: CGFloat(x), y: CGFloat(y))) ? 1 : 0)
            }
        }

        return count
    }

    private func isPixelBlack(_ point: CGPoint) -> Bool {
        let pixelData = cgImage?.dataProvider?.data
        let pointerData: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

        let pixelInfo = Int(((size.width * point.y) + point.x)) * 4

        let maxValue: CGFloat = 255.0
        let compare: CGFloat = 0.01

        if (CGFloat(pointerData[pixelInfo]) / maxValue) > compare { return false }
        if (CGFloat(pointerData[pixelInfo + 1]) / maxValue) > compare { return false }
        if (CGFloat(pointerData[pixelInfo + 2]) / maxValue) > compare { return false }

        return true
    }
}

您可以使用以下方式调用此功能:
let count = image.blackPixelCount

唯一的限制是,即使在小图像上也是一个非常缓慢的过程。


1
你正在为每个像素做大量的工作。一定有更有效率的方法;例如,用一次调用将图像的RGB数据读入数组中,然后扫描该数组... - Nicolas Miari
@NicolasMiari 我同意,这也是为什么我在底部加了一个警告的原因。最近我也做了类似的事情,但是针对非常小的图像,所以我可以承受性能损失。然而,我很想看到更快的东西。 - CodeBender
我不记得UIImage有哪些API可以访问原始数据,但我打赌CGImageRef更适合... - Nicolas Miari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接