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

2
정확하더라도. count가 큰 경우 (이미지 행의 길이 또는 전체 이미지 크기와 같은) UIUI 객체가 너무 많으면 실제로이 방법의 성능이 좋지 않습니다. 실제로 픽셀이 필요할 때 UIColor는 괜찮습니다 (UIColors의 NSArray는 너무 많습니다). 그리고 많은 색상이 필요할 때 OpenGL 등을 사용하기 때문에 UIColors를 사용하지 않습니다. 제 생각에이 방법은 실제 사용은 없지만 교육 목적에 매우 좋습니다.
nacho4d

4
한 픽셀을 읽을 수있는 큰 공간은 너무 번거 롭습니다.
ragnarius

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

이를 수행하는 한 가지 방법은 이미지를 주어진 색상 공간 (이 경우 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);
Adam Waite

5
CGContextDrawImage세 개의 인수가 필요하고 두 개 이상의 인수 만 제공됩니다 ... 나는 그렇게 생각합니다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의 솔루션.
Tyler

1
이 방법은 이미지를 컬러 스페이스 RGBA로 내 앱에서 저장하더라도 항상 BGR 형식을 제공합니다.
Soumyajit

1
@Soumyaljit CGImageRef 데이터가 원래 저장된 형식으로 데이터를 복사합니다. 대부분의 경우 이것은 BGRA이지만 YUV 일 수도 있습니다.
Cameron Lowell Palmer

18

여기에 하나의 정답없다는 것을 믿을 수 없었 습니다. 포인터를 할당 할 필요가 없으며 곱하지 않은 값은 여전히 ​​정규화해야합니다. 추격을 자르려면 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 (이름 : "greencolorImg.png")
Sagar Sukode

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

것을 명심 pt1하고 pt2있습니다 CGPoint변수.
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 Reference에 따르면 객체는 변경할 수 없으며 백업 바이트에 액세스 할 수 없습니다. 를 (명시 적으로 또는 암시 적으로) 채운 경우 CGImage 데이터에 액세스 할 수있는 것이 사실이지만, UIImagea 로 뒷받침 되면 그 반대로 CGImage반환 NULL됩니다 .UIImageCIImage

이미지 객체는 기본 이미지 데이터에 직접 액세스 할 수 없습니다. 그러나 앱에서 사용하기 위해 다른 형식으로 이미지 데이터를 검색 할 수 있습니다. 특히 cgImage 및 ciImage 속성을 사용하여 각각 Core Graphics 및 Core Image와 호환되는 이미지 버전을 검색 할 수 있습니다. UIImagePNGRepresentation ( :) 및 UIImageJPEGRepresentation ( : _ :) 함수를 사용하여 PNG 또는 JPEG 형식의 이미지 데이터를 포함하는 NSData 객체를 생성 할 수도 있습니다 .

이 문제를 해결하는 일반적인 방법

명시된 바와 같이 귀하의 옵션은

  • UIImagePNGRepresentation 또는 JPEG
  • 이미지에 CGImage 또는 CIImage 백업 데이터가 있는지 확인하고이를 가져옵니다.

ARGB, PNG 또는 JPEG 데이터가 아닌 출력을 원하고 데이터가 이미 CIImage에 의해 백업되지 않은 경우에는 이들 중 어느 것도 특히 유용한 트릭이 아닙니다.

내 추천, CIImage를 사용해보십시오

프로젝트를 개발하는 동안 UIImage를 완전히 피하고 다른 것을 선택하는 것이 더 합리적 일 수 있습니다. Obj-C 이미지 래퍼 인 UIImage는 종종 CGImage에 의해 당연한 것으로 간주됩니다. CIImage는 CIContext 를 사용하여 원하는 형식을 만들지 않고도 원하는 형식을 얻을 수 있다는 점에서 더 나은 래퍼 형식입니다 . 귀하의 경우 비트 맵을 얻는 것은 전화의 문제입니다

-렌더 : 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

스위프트 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픽셀 방향을 읽기 전에 수행 할 작업 의 방향을 정규화하는 방법에 대한 리소스가 많이 있습니다 .
데스몬드 ume

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.