UIImage (Cocoa Touch) veya CGImage'dan (Çekirdek Grafikler) piksel verileri nasıl alınır?


200

Bir UIImage (Kakao Dokunuş) var. Bundan sonra, bir CGImage veya mevcut olmasını istediğiniz başka bir şey almaktan mutluluk duyuyorum. Bu işlevi yazmak istiyorum:

- (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;
}

Yanıtlar:


249

FYI, Keremk'in cevabını orijinal taslak ile birleştirdim, yazım hatalarını temizledim, bir dizi renk döndürmek için genelleştirdim ve her şeyi derledim. İşte sonuç:

+ (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
Renkleri düzeltmeyi unutma. Ayrıca, bu 1 ile çarpmalar hiçbir şey yapmaz.
Peter Hosey

2
Doğru olsa bile. Sayı çok sayıda olduğunda (görüntünün bir satırının uzunluğu veya tüm görüntü boyutu gibi!) Bu yöntemin performansı iyi olmayacaktır çünkü çok fazla UIColor nesnesine sahip olmak gerçekten ağırdır. Gerçek hayatta bir piksele ihtiyacım olduğunda UIColor tamam, (UIColors'un bir NSArray'ı benim için çok fazla). Ve bir sürü renk gerektiğinde UIColors kullanmam çünkü muhtemelen OpenGL, vb. Kanımca bu yöntemin gerçek yaşam kullanımı yoktur, ancak eğitim amaçlı çok iyidir.
nacho4d

4
Çok hantal bir nokta sadece bir piksel okumak için büyük bir boşluk malloc.
ragnarius

46
MALLOC'UN CALLOC INSTEAD KULLANIN !!!! Bu kodu belirli piksellerin saydam olup olmadığını test etmek için kullanıyordum ve ilk önce bellek temizlenmediği için piksellerin saydam olması gerektiği sahte bilgileri geri alıyordum. Malloc yerine calloc (width * height, 4) kullanmak hile yaptı. Kodun geri kalanı harika, TEŞEKKÜRLER!
DonnaLea

5
Bu harika. Teşekkürler. Kullanımla ilgili not: x ve y, RGBA'ları almaya başlamak için görüntü içindeki koordinatlardır ve 'sayı', o noktadan elde edilecek, soldan sağa ve ardından satır satırdaki piksel sayısıdır. Örnek Kullanım: Bir görüntünün tamamı için RGBA'ları almak için: Görüntünün getRGBAsFromImage:image atX:0 andY:0 count:image.size.width*image.size.height son satırı için RGBA'ları almak için: getRGBAsFromImage:image atX:0 andY:image.size.height-1 count:image.size.width
Harris

49

Bunu yapmanın bir yolu, görüntüyü belirli bir renk alanı için belirli bir arabellek tarafından desteklenen bir bitmap bağlamına çizmektir (bu durumda RGB'dir): (bunun görüntü verilerini bu arabelleğe kopyalayacağını unutmayın. piksel değerlerini her almanız gerektiğinde bu işlemi yapmak yerine önbelleğe almak istiyorsanız)

Örnek olarak aşağıya bakın:

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

Uygulamamda benzer bir şey yaptım. Rasgele bir piksel ayıklamak için, bilinen bir bitmap formatına sahip 1x1 bitmap'e çizim yapıp CGBitmapContext öğesinin kaynağını uygun şekilde ayarlayabilirsiniz.
Mark Bessey

5
Bu bellek sızdırıyor. RawData öğesini serbest bırakın: free (rawData);
Adam Waite

6
CGContextDrawImageüç argüman gerektirir ve sadece iki yukarıda verilir ... Bence olmalıCGContextDrawImage(context, CGRectMake(0, 0, width, height), image)
user1135469

25

Apple'ın Teknik Soru ve Cevap QA1509 aşağıdaki basit yaklaşımı gösterir:

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

CFDataGetBytePtrGerçek baytlara (ve CGImageGet*bunları nasıl yorumlayacağınızı anlamak için çeşitli yöntemlere) ulaşmak için kullanın .


10
Bu harika bir yaklaşım değildir, çünkü piksel formatı görüntü başına çok değişme eğilimindedir. Değiştirilebilecek çeşitli şeyler vardır 1. Görüntünün yönü 2. Alfa bileşeninin biçimi ve 3. RGB bayt sırası. Kişisel olarak Apple'ın dokümanlarını bunun nasıl yapılacağı konusunda kodunu çözmek için biraz zaman harcadım, ama buna değdiğinden emin değilim. Eğer hızlı yapılmasını istiyorsanız, sadece keremk'in çözümü.
Tyler

1
Bu yöntem, görüntüyü
kaydetmiş olsam

1
@Soumyaljit Bu, verileri CGImageRef verilerinin orijinal olarak saklandığı biçimde kopyalayacaktır. Çoğu durumda bu BGRA olacaktır, ancak YUV da olabilir.
Cameron Lowell Palmer

18

Burada tek bir doğru cevap olmadığına inanamadım . İşaretçiler tahsis etmeye gerek yoktur ve çarpılmayan değerlerin hala normalleştirilmesi gerekir. Sadede, burada İçin Swift 4. için doğru versiyonu UIImagesadece kullanım .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)
        }
    }
}

Öncelikle görüntüyü bir ara belleğe çizmeniz / dönüştürmeniz gerekmesinin nedeni, görüntülerin birkaç farklı biçime sahip olabilmesidir. Bu adım, okuyabileceğiniz tutarlı bir biçime dönüştürmek için gereklidir.


Bu uzantıyı resmim için nasıl kullanabilirim? let imageObj = UIImage (adlı: "greencolorImg.png")
Sagar Sukode

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

akılda tutmak pt1ve pt2olan CGPointdeğişkenler.
AnBisw

günümü kurtardım :)
Dari

1
Bir görüntünün her pikselinin rengini almak istersem @ErikAigner bu işe yarar mı? performans maliyeti nedir?
Ameet Dhas

9

İşte @Matt'ın, istenen pikselin bağlamdaki bir pikselle hizalanması için görüntüyü değiştirerek yalnızca istenen pikseli 1x1 içeriğe dönüştürdüğü bir SO iş parçacığı .


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

Bunu yayınladım ve benim için çalıştı, ancak tampondan UIImage'a geri dönerek henüz bir fikir bulamadım, herhangi bir fikir?
Nidal Fakhouri

8

UIImage, baytların CGImage veya CIImage olduğu bir sarıcıdır

UIImage'daki Apple Reference'a göre nesne değişmez ve destek baytlarına erişiminiz yok. Eğer nüfuslu eğer CGImage verilere erişebilir doğru olmakla birlikte UIImagebir CGImage(açık veya üstü örtülü), bu dönecektir NULLeğer UIImagebir tarafından desteklenmektedir CIImageve tersi de geçerlidir.

Görüntü nesneleri temeldeki görüntü verilerine doğrudan erişim sağlamaz. Ancak, uygulamanızda kullanmak üzere görüntü verilerini başka biçimlerde alabilirsiniz. Özellikle, görüntünün sırasıyla Çekirdek Grafikler ve Çekirdek Görüntü ile uyumlu sürümlerini almak için cgImage ve ciImage özelliklerini kullanabilirsiniz. PNG veya JPEG biçiminde görüntü verilerini içeren bir NSData nesnesi oluşturmak için UIImagePNGRepresentation ( :) ve UIImageJPEGRepresentation ( : _ :) işlevlerini de kullanabilirsiniz .

Bu sorunu aşmanın ortak püf noktaları

Belirtildiği gibi seçenekleriniz

  • UIImagePNGösterim veya JPEG
  • Görüntünün CGImage veya CIImage destek verisi olup olmadığını belirleyin ve oraya getirin

ARGB, PNG veya JPEG verisi olmayan çıktılar istiyorsanız ve veriler zaten CIImage tarafından desteklenmiyorsa, bunların hiçbiri özellikle iyi hileler değildir.

Benim tavsiyem CIImage'ı dene

Projenizi geliştirirken, UIImage'dan tamamen kaçınmanız ve başka bir şey seçmeniz daha mantıklı olabilir. Bir Obj-C görüntü sarıcısı olarak UIImage, CGImage tarafından genellikle verildiği noktaya kadar desteklenir. CIImage , nasıl oluşturulduğunu bilmek zorunda kalmadan istediğiniz formatı elde etmek için bir CIContext kullanabilmeniz açısından daha iyi bir sarmalayıcı formatı olma eğilimindedir . Sizin durumunuzda, bitmap'i almak bir arama meselesi olacaktır

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

Ek bir bonus olarak, görüntüye filtreler bağlayarak görüntüye güzel manipülasyonlar yapmaya başlayabilirsiniz. Bu, görüntünün baş aşağı olduğu veya döndürülmesi / ölçeklendirilmesi vb. Gereken birçok sorunu çözer.


6

Olie ve Algal'ın cevabına dayanarak, Swift 3 için güncellenmiş bir cevap

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 sürümü

Burada verilen cevaplar güncel değil veya yanlış çünkü aşağıdakileri dikkate almıyorlar:

  1. Görüntü piksel boyutu tarafından döndürülen olan nokta boyutu farklı olabilir image.size.width/ ' image.size.height.
  2. Görüntüde BGRA, ABGR, ARGB vb. Gibi piksel bileşenleri tarafından kullanılan çeşitli düzenler olabilir veya BGR ve RGB gibi bir alfa bileşenine sahip olmayabilir. Örneğin, UIView.drawHierarchy(in:afterScreenUpdates:)yöntem BGRA görüntüleri üretebilir.
  3. Renk bileşenleri görüntüdeki tüm pikseller için alfa ile önceden çarpılabilir ve orijinal rengi geri yüklemek için alfaya bölünmesi gerekir.
  4. Tarafından kullanılan bellek optimizasyonu CGImageiçin, bayt cinsinden piksel satırının boyutu yalnızca piksel genişliğinin 4 ile çarpılmasından daha büyük olabilir.

Aşağıdaki kod, UIColortüm bu özel durumlar için bir pikselden yararlanmak için evrensel bir Swift 5 çözümü sağlamaktır . Kod, performans için değil kullanılabilirlik ve netlik için optimize edilmiştir .

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
    }

}

Yönelimdeki değişiklikler ne olacak? UIImage.Orientation'u da kontrol etmenize gerek yok mu?
endavid

Ayrıca, componentLayoutsadece alfa olduğunda da vaktiniz eksik .alphaOnly.
endavid

@endavid "Yalnızca Alfa" desteklenmez (çok nadir görülen bir durum olduğu için). Görüntü yönlendirmesi bu kodun kapsamı dışındadır, ancak a'nın yönünü normalleştirme hakkında çok fazla kaynak vardır UIImage, bu da piksel renklerini okumaya başlamadan önce yapacağınız şeydir.
Desmond Hume

2

Farklı cevaplara dayanarak ama esas olarak buna dayanarak , bu ihtiyacım olan şey için çalışıyor:

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

Bu satırların bir sonucu olarak, AARRGGBB formatında bir pikseliniz olacak ve alfa her zaman 4 bayt işaretsiz tamsayıda FF olarak ayarlanacaktır pixel1.


1

Swift 5'teki bir UIImage'ın ham RGB değerlerine erişmek için altta yatan CGImage ve onun dataProvider'ı kullanın:

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/

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.