UIImage(Cocoa Touch)またはCGImage(Core Graphics)からピクセルデータを取得する方法


200

UIImage(Cocoa Touch)を持っています。そこから、CGImageなど、入手したいものを入手できてうれしいです。この関数を書きたい:

- (int)getRGBAFromImage:(UIImage *)image atX:(int)xx andY:(int)yy {
  // [...]
  // What do I want to read about to help
  // me fill in this bit, here?
  // [...]

  int result = (red << 24) | (green << 16) | (blue << 8) | alpha;
  return result;
}

回答:


249

ちなみに、私はKeremkの回答を元のアウトラインと組み合わせ、タイプミスをクリーンアップし、色の配列を返すように一般化して、全体をコンパイルしました。結果は次のとおりです。

+ (NSArray*)getRGBAsFromImage:(UIImage*)image atX:(int)x andY:(int)y count:(int)count
{
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];

    // First get the image into your data buffer
    CGImageRef imageRef = [image CGImage];
    NSUInteger width = CGImageGetWidth(imageRef);
    NSUInteger height = CGImageGetHeight(imageRef);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
    NSUInteger bytesPerPixel = 4;
    NSUInteger bytesPerRow = bytesPerPixel * width;
    NSUInteger bitsPerComponent = 8;
    CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                    bitsPerComponent, bytesPerRow, colorSpace,
                    kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);

    // Now your rawData contains the image data in the RGBA8888 pixel format.
    NSUInteger byteIndex = (bytesPerRow * y) + x * bytesPerPixel;
    for (int i = 0 ; i < count ; ++i)
    {
        CGFloat alpha = ((CGFloat) rawData[byteIndex + 3] ) / 255.0f;
        CGFloat red   = ((CGFloat) rawData[byteIndex]     ) / alpha;
        CGFloat green = ((CGFloat) rawData[byteIndex + 1] ) / alpha;
        CGFloat blue  = ((CGFloat) rawData[byteIndex + 2] ) / alpha;
        byteIndex += bytesPerPixel;

        UIColor *acolor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
        [result addObject:acolor];
    }

  free(rawData);

  return result;
}

4
色を非乗算することを忘れないでください。また、これらの1の乗算は何もしません。
Peter Hosey、2014年

2
たとえそれが正しいとしても。countが大きい場合(画像の行の長さや画像全体のサイズなど)、UIColorオブジェクトが多すぎると本当に重いため、このメソッドのパフォーマンスは良くありません。実際にピクセルが必要な場合、UIColorは問題ありません(UIColorsのNSArrayは私には多すぎます)。そして、たくさんの色が必要なときは、おそらくOpenGLなどを使用するので、UIColorsは使用しません。私の意見では、この方法には実際の使用法はありませんが、教育目的には非常に適しています。
nacho4d 2011年

4
面倒すぎて、1ピクセルを読み取るだけの大きなスペースをmallocします。
ラグナリウス

46
MALLOCの代わりにCALLOCを使用してください!!!! このコードを使用して、特定のピクセルが透明であるかどうかをテストし、メモリが最初にクリアされなかったためにピクセルが透明であることを意図した偽の情報を取得していました。mallocの代わりにcalloc(width * height、4)を使用するとうまくいきました。残りのコードは素晴らしいです、ありがとう!
DonnaLea

5
これは素晴らしい。ありがとう。使用上の注意:xとyはRGBAの取得を開始する画像内の座標であり、「count」はそのポイントから取得するピクセル数であり、左から右に、行ごとに続きます。使用例:画像全体のRGBAを取得するには:画像 getRGBAsFromImage:image atX:0 andY:0 count:image.size.width*image.size.height の最後の行のRGBAを取得するには: getRGBAsFromImage:image atX:0 andY:image.size.height-1 count:image.size.width
Harris

49

これを行う1つの方法は、特定の色空間の特定のバッファー(この場合はRGB)に対応するビットマップコンテキストに画像を描画することです(これにより、画像データがそのバッファーにコピーされるため、ピクセル値を取得する必要があるたびにこの操作を実行するのではなく、キャッシュしたい)

サンプルとして以下を参照してください:

// First get the image into your data buffer
CGImageRef image = [myUIImage CGImage];
NSUInteger width = CGImageGetWidth(image);
NSUInteger height = CGImageGetHeight(image);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = malloc(height * width * 4);
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);

CGContextDrawImage(context, CGRectMake(0, 0, width, height));
CGContextRelease(context);

// Now your rawData contains the image data in the RGBA8888 pixel format.
int byteIndex = (bytesPerRow * yy) + xx * bytesPerPixel;
red = rawData[byteIndex];
green = rawData[byteIndex + 1];
blue = rawData[byteIndex + 2];
alpha = rawData[byteIndex + 3];

アプリでも同様のことを行いました。任意のピクセルを抽出するには、既知のビットマップ形式の1x1ビットマップに描画し、CGBitmapContextの原点を適切に調整します。
Mark Bessey、

5
これにより、メモリがリークします。rawDataを解放:free(rawData);
アダムウェイト2013年

5
CGContextDrawImage3つの引数が必要で、上記の2つしか指定されていません...必要があると思いますCGContextDrawImage(context, CGRectMake(0, 0, width, height), image)
user1135469

25

AppleのテクニカルQ&A QA1509は、次の簡単なアプローチを示しています。

CFDataRef CopyImagePixels(CGImageRef inImage)
{
    return CGDataProviderCopyData(CGImageGetDataProvider(inImage));
}

CFDataGetBytePtr実際のバイト(およびCGImageGet*それらを解釈する方法を理解するためのさまざまな方法)を取得するために使用します。


10
ピクセル形式は画像ごとに大きく異なる傾向があるため、これは優れたアプローチではありません。1.画像の向き2.アルファコンポーネントの形式3. RGBのバイトオーダーなど、変更できるものはいくつかあります。私は個人的に、これを行う方法に関するAppleのドキュメントを解読するためにある程度の時間を費やしましたが、それが価値があるかどうかはわかりません。早く実行したい場合は、keremkのソリューションを使用してください。
タイラー

1
この方法では、画像を(色空間RGBAでアプリ内から)保存した場合でも、常にBGR形式が得られます
Soumyajit

1
@Soumyaljitこれは、CGImageRefデータが最初に格納された形式でデータをコピーします。ほとんどの場合、これはBGRAになりますが、YUVになることもあります。
Cameron Lowell Palmer、

18

ここに正解が1つだけではないと信じられませんでした。ポインタを割り当てる必要はなく、乗算されていない値は正規化する必要があります。追いかけるために、Swift 4の正しいバージョンを以下UIImageに示します.cgImage

extension CGImage {
    func colors(at: [CGPoint]) -> [UIColor]? {
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bytesPerPixel = 4
        let bytesPerRow = bytesPerPixel * width
        let bitsPerComponent = 8
        let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue

        guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo),
            let ptr = context.data?.assumingMemoryBound(to: UInt8.self) else {
            return nil
        }

        context.draw(self, in: CGRect(x: 0, y: 0, width: width, height: height))

        return at.map { p in
            let i = bytesPerRow * Int(p.y) + bytesPerPixel * Int(p.x)

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

            return UIColor(red: r, green: g, blue: b, alpha: a)
        }
    }
}

最初に画像を描画/バッファに変換する必要があるのは、画像にはいくつかの異なる形式があるためです。この手順は、読み取り可能な一貫した形式に変換するために必要です。


この拡張機能を画像に使用するにはどうすればよいですか?let imageObj = UIImage(named: "greencolorImg.png")
Sagar Sukode

let imageObj = UIImage(named:"greencolorImg.png"); imageObj.cgImage?.colors(at: [pt1, pt2])
Erik Aigner

pt1pt2CGPoint変数であることに注意してください。
AnBisw

私の日を保存しました:)
Dari

1
@ErikAignerは、画像の各ピクセルの色を取得したい場合に機能しますか?パフォーマンスコストはどれくらいですか?
Ameet Dhas

9

これは、@ Mattが画像を移動して1x1コンテキストに目的のピクセルのみをレンダリングするSO スレッドです。


9
NSString * path = [[NSBundle mainBundle] pathForResource:@"filename" ofType:@"jpg"];
UIImage * img = [[UIImage alloc]initWithContentsOfFile:path];
CGImageRef image = [img CGImage];
CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(image));
const unsigned char * buffer =  CFDataGetBytePtr(data);

私はこれを投稿しましたが、うまくいきましたが、バッファからUIImageに戻ると、まだ理解できていないようです。
Nidal Fakhouri

8

UIImageは、バイトがCGImageまたはCIImageのラッパーです。

UIImageAppleリファレンスによると、オブジェクトは不変であり、バッキングバイトにアクセスできません。に(明示的または暗黙的に)を設定UIImageした場合にCGImageデータにアクセスできることは事実ですが、がによってサポートされている場合はがCGImage返され、逆の場合も同様です。NULLUIImageCIImage

画像オブジェクトは、基礎となる画像データへの直接アクセスを提供しません。ただし、画像データを他の形式で取得してアプリで使用することはできます。具体的には、cgImageプロパティとciImageプロパティを使用して、それぞれCore GraphicsおよびCore Imageと互換性のあるイメージのバージョンを取得できます。UIImagePNGRepresentation(:)およびUIImageJPEGRepresentation(:_ :)関数を使用して、PNGまたはJPEG形式の画像データを含むNSDataオブジェクトを生成することもできます。

この問題を回避するための一般的なトリック

述べたようにあなたのオプションは

  • UIImagePNGRepresentationまたはJPEG
  • 画像にCGImageまたはCIImageのバッキングデータがあるかどうかを判別し、そこに取得する

ARGB、PNG、またはJPEGデータではない出力が必要で、データがまだCIImageによってサポートされていない場合、これらはどちらも特に優れたトリックではありません。

私の推奨、CIImageを試す

プロジェクトを開発している間は、UIImageを完全に回避して別のものを選択する方が理にかなっている場合があります。UIImageは、Obj-C画像ラッパーとして、CGImageによって、当然のことと思われるところまで裏付けられています。CIImageは、CIContextを使用して、それがどのように作成されたかを知る必要なく、希望する形式を取得できるという点で、より優れたラッパー形式である傾向があります。あなたの場合、ビットマップを取得することは呼び出しの問題でしょう

-render:toBitmap:rowBytes:bounds:format:colorSpace:

追加のボーナスとして、フィルターを画像にチェーンすることにより、画像に優れた操作を開始できます。これにより、画像が上下逆になったり、回転/スケーリングが必要になるなどの多くの問題が解決されます。


6

OlieとAlgalの答えに基づいて、ここにSwift 3の更新された答えがあります

public func getRGBAs(fromImage image: UIImage, x: Int, y: Int, count: Int) -> [UIColor] {

var result = [UIColor]()

// First get the image into your data buffer
guard let cgImage = image.cgImage else {
    print("CGContext creation failed")
    return []
}

let width = cgImage.width
let height = cgImage.height
let colorSpace = CGColorSpaceCreateDeviceRGB()
let rawdata = calloc(height*width*4, MemoryLayout<CUnsignedChar>.size)
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width
let bitsPerComponent = 8
let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue

guard let context = CGContext(data: rawdata, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
    print("CGContext creation failed")
    return result
}

context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

// Now your rawData contains the image data in the RGBA8888 pixel format.
var byteIndex = bytesPerRow * y + bytesPerPixel * x

for _ in 0..<count {
    let alpha = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 3, as: UInt8.self)) / 255.0
    let red = CGFloat(rawdata!.load(fromByteOffset: byteIndex, as: UInt8.self)) / alpha
    let green = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 1, as: UInt8.self)) / alpha
    let blue = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 2, as: UInt8.self)) / alpha
    byteIndex += bytesPerPixel

    let aColor = UIColor(red: red, green: green, blue: blue, alpha: alpha)
    result.append(aColor)
}

free(rawdata)

return result
}


3

Swift 5バージョン

ここでの回答は、次のことを考慮に入れていないため、古くなっているか間違っています。

  1. 画像のピクセルサイズは、image.size.width/ によって返されるポイントサイズと異なる場合がありますimage.size.height
  2. BGRA、ABGR、ARGBなど、画像のピクセルコンポーネントで使用されるさまざまなレイアウトがあり、BGRやRGBなどのアルファコンポーネントがまったくない場合もあります。たとえば、UIView.drawHierarchy(in:afterScreenUpdates:)メソッドはBGRA画像を生成できます。
  3. 画像のすべてのピクセルについて、色成分にアルファを事前に乗算できます。元の色を復元するには、アルファで除算する必要があります。
  4. が使用するメモリ最適化CGImageでは、ピクセル行のバイト単位のサイズは、ピクセル幅を4倍するだけの場合よりも大きくなる可能性があります。

以下のコードはUIColor、そのようなすべての特殊なケースでピクセルを取得するためのユニバーサルSwift 5ソリューションを提供するものです。コードは、パフォーマンスではなく、使いやすさと明確さのために最適化されています。

public extension UIImage {

    var pixelWidth: Int {
        return cgImage?.width ?? 0
    }

    var pixelHeight: Int {
        return cgImage?.height ?? 0
    }

    func pixelColor(x: Int, y: Int) -> UIColor {
        assert(
            0..<pixelWidth ~= x && 0..<pixelHeight ~= y,
            "Pixel coordinates are out of bounds")

        guard
            let cgImage = cgImage,
            let data = cgImage.dataProvider?.data,
            let dataPtr = CFDataGetBytePtr(data),
            let colorSpaceModel = cgImage.colorSpace?.model,
            let componentLayout = cgImage.bitmapInfo.componentLayout
        else {
            assertionFailure("Could not get a pixel of an image")
            return .clear
        }

        assert(
            colorSpaceModel == .rgb,
            "The only supported color space model is RGB")
        assert(
            cgImage.bitsPerPixel == 32 || cgImage.bitsPerPixel == 24,
            "A pixel is expected to be either 4 or 3 bytes in size")

        let bytesPerRow = cgImage.bytesPerRow
        let bytesPerPixel = cgImage.bitsPerPixel/8
        let pixelOffset = y*bytesPerRow + x*bytesPerPixel

        if componentLayout.count == 4 {
            let components = (
                dataPtr[pixelOffset + 0],
                dataPtr[pixelOffset + 1],
                dataPtr[pixelOffset + 2],
                dataPtr[pixelOffset + 3]
            )

            var alpha: UInt8 = 0
            var red: UInt8 = 0
            var green: UInt8 = 0
            var blue: UInt8 = 0

            switch componentLayout {
            case .bgra:
                alpha = components.3
                red = components.2
                green = components.1
                blue = components.0
            case .abgr:
                alpha = components.0
                red = components.3
                green = components.2
                blue = components.1
            case .argb:
                alpha = components.0
                red = components.1
                green = components.2
                blue = components.3
            case .rgba:
                alpha = components.3
                red = components.0
                green = components.1
                blue = components.2
            default:
                return .clear
            }

            // If chroma components are premultiplied by alpha and the alpha is `0`,
            // keep the chroma components to their current values.
            if cgImage.bitmapInfo.chromaIsPremultipliedByAlpha && alpha != 0 {
                let invUnitAlpha = 255/CGFloat(alpha)
                red = UInt8((CGFloat(red)*invUnitAlpha).rounded())
                green = UInt8((CGFloat(green)*invUnitAlpha).rounded())
                blue = UInt8((CGFloat(blue)*invUnitAlpha).rounded())
            }

            return .init(red: red, green: green, blue: blue, alpha: alpha)

        } else if componentLayout.count == 3 {
            let components = (
                dataPtr[pixelOffset + 0],
                dataPtr[pixelOffset + 1],
                dataPtr[pixelOffset + 2]
            )

            var red: UInt8 = 0
            var green: UInt8 = 0
            var blue: UInt8 = 0

            switch componentLayout {
            case .bgr:
                red = components.2
                green = components.1
                blue = components.0
            case .rgb:
                red = components.0
                green = components.1
                blue = components.2
            default:
                return .clear
            }

            return .init(red: red, green: green, blue: blue, alpha: UInt8(255))

        } else {
            assertionFailure("Unsupported number of pixel components")
            return .clear
        }
    }

}

public extension UIColor {

    convenience init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
        self.init(
            red: CGFloat(red)/255,
            green: CGFloat(green)/255,
            blue: CGFloat(blue)/255,
            alpha: CGFloat(alpha)/255)
    }

}

public extension CGBitmapInfo {

    enum ComponentLayout {

        case bgra
        case abgr
        case argb
        case rgba
        case bgr
        case rgb

        var count: Int {
            switch self {
            case .bgr, .rgb: return 3
            default: return 4
            }
        }

    }

    var componentLayout: ComponentLayout? {
        guard let alphaInfo = CGImageAlphaInfo(rawValue: rawValue & Self.alphaInfoMask.rawValue) else { return nil }
        let isLittleEndian = contains(.byteOrder32Little)

        if alphaInfo == .none {
            return isLittleEndian ? .bgr : .rgb
        }
        let alphaIsFirst = alphaInfo == .premultipliedFirst || alphaInfo == .first || alphaInfo == .noneSkipFirst

        if isLittleEndian {
            return alphaIsFirst ? .bgra : .abgr
        } else {
            return alphaIsFirst ? .argb : .rgba
        }
    }

    var chromaIsPremultipliedByAlpha: Bool {
        let alphaInfo = CGImageAlphaInfo(rawValue: rawValue & Self.alphaInfoMask.rawValue)
        return alphaInfo == .premultipliedFirst || alphaInfo == .premultipliedLast
    }

}

向きの変化はどうですか?UIImage.Orientationも確認する必要はありませんか?
endavid

また、componentLayoutアルファのみの場合は、ケースがありません.alphaOnly
endavid

@endavid "Alpha only"はサポートされていません(非常にまれなケースのため)。画像の向きはこのコードの範囲外ですが、の向きを正規化する方法に関するリソースはたくさんありUIImageます。これは、ピクセルカラーの読み取りを開始する前に行うことです。
Desmond Hume

2

さまざまな回答に基づいていますが、主にこれに基づいて、これは私が必要とするものに適しています:

UIImage *image1 = ...; // The image from where you want a pixel data
int pixelX = ...; // The X coordinate of the pixel you want to retrieve
int pixelY = ...; // The Y coordinate of the pixel you want to retrieve

uint32_t pixel1; // Where the pixel data is to be stored
CGContextRef context1 = CGBitmapContextCreate(&pixel1, 1, 1, 8, 4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipFirst);
CGContextDrawImage(context1, CGRectMake(-pixelX, -pixelY, CGImageGetWidth(image1.CGImage), CGImageGetHeight(image1.CGImage)), image1.CGImage);
CGContextRelease(context1);

この行の結果として、4バイトの符号なし整数でアルファが常にFFに設定されたAARRGGBB形式のピクセルができますpixel1


1

Swift 5でUIImageの生のRGB値にアクセスするには、基になるCGImageとそのdataProviderを使用します。

import UIKit

let image = UIImage(named: "example.png")!

guard let cgImage = image.cgImage,
    let data = cgImage.dataProvider?.data,
    let bytes = CFDataGetBytePtr(data) else {
    fatalError("Couldn't access image data")
}
assert(cgImage.colorSpace?.model == .rgb)

let bytesPerPixel = cgImage.bitsPerPixel / cgImage.bitsPerComponent
for y in 0 ..< cgImage.height {
    for x in 0 ..< cgImage.width {
        let offset = (y * cgImage.bytesPerRow) + (x * bytesPerPixel)
        let components = (r: bytes[offset], g: bytes[offset + 1], b: bytes[offset + 2])
        print("[x:\(x), y:\(y)] \(components)")
    }
    print("---")
}

https://www.ralfebert.de/ios/examples/image-processing/uiimage-raw-pixels/

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.