Jak uzyskać komponent Y Z CMSampleBuffer wynikający z AVCaptureSession?

Hej, próbuję uzyskać dostęp do surowych danych z aparatu iphone za pomocą Avcapturession. Podążam za przewodnikiem dostarczonym przez Apple (link tutaj ).

Surowe dane z samplebuffera są w formacie YUV(czy mam rację co do formatu raw klatki wideo?? ), jak bezpośrednio uzyskać dane dla komponentu Y z surowych danych przechowywanych w buforze samplebuffer.

Author: Nihao, 2010-11-03

4 answers

Podczas konfigurowania AVCaptureVideoDataOutput, który zwraca surowe klatki aparatu, można ustawić format ramek za pomocą następującego kodu:

[videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];

W tym przypadku podany jest format pikseli BGRA (użyłem go do dopasowania formatu kolorów dla tekstury OpenGL ES). Każdy piksel w tym formacie ma jeden bajt dla niebieskiego, zielonego, czerwonego i alfa, w tej kolejności. Korzystanie z tego ułatwia wyciąganie komponentów kolorowych, ale poświęcasz trochę wydajności, potrzebując konwersja z kamery-natywny YUV colorspace.

Inne obsługiwane przestrzenie kolorów to kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange i kCVPixelFormatType_420YpCbCr8BiPlanarFullRange na nowszych urządzeniach oraz kCVPixelFormatType_422YpCbCr8 na iPhonie 3G. przyrostek VideoRange lub FullRange po prostu wskazuje, czy bajty są zwracane między 16 - 235 dla Y i 16 - 240 dla UV lub Pełne 0 - 255 dla każdego komponentu.

Uważam, że domyślną przestrzenią kolorów używaną przez instancję AVCaptureVideoDataOutput jest planarna przestrzeń kolorów YUV 4:2:0 (z wyjątkiem iPhone 3G, gdzie jest to YUV 4:2:2 interleaved). Oznacza to, że w ramce wideo znajdują się dwie płaszczyzny danych obrazu, przy czym najpierw pojawia się płaszczyzna Y. Dla każdego piksela wynikowego obrazu istnieje jeden bajt dla wartości Y w tym pikselu.

Otrzymałbyś te surowe dane y, implementując coś takiego w Twoim wywołaniu zwrotnym delegata:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);

    unsigned char *rawPixelBase = (unsigned char *)CVPixelBufferGetBaseAddress(pixelBuffer);

    // Do something with the raw pixels here

    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
}

Można następnie określić położenie w danych ramki dla każdej współrzędnej X, Y na obrazie i wyciągnąć bajt, który odpowiada składowej Y na tym koordynować.

Próbka FindMyiCone firmy Apple z WWDC 2010 (dostępna wraz z filmami) pokazuje, jak przetwarzać surowe dane BGRA z każdej klatki. Stworzyłem również przykładową aplikację, którą możesz pobrać kod do tutaj , która wykonuje śledzenie obiektów w Kolorze za pomocą wideo na żywo z aparatu iPhone ' a. Oba pokazują, jak przetwarzać surowe dane pikseli, ale żaden z nich nie działa w przestrzeni kolorów YUV.

 20
Author: Brad Larson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-03 16:01:23

Oprócz odpowiedzi Brada i własnego kodu, warto rozważyć następujące:

Ponieważ obraz ma dwie oddzielne płaszczyzny, Funkcja cvpixelbuffergetbaseaddress nie zwróci adresu bazowego płaszczyzny, ale adresu bazowego dodatkowej struktury danych. Prawdopodobnie ze względu na obecną implementację otrzymujesz adres wystarczająco blisko pierwszej płaszczyzny, abyś mógł zobaczyć obraz. Ale to jest powód, dla którego jest przesunięty i ma śmieci na górze w lewo. Prawidłowym sposobem otrzymania pierwszej płaszczyzny jest:

unsigned char *rowBase = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);

Wiersz na obrazku może być dłuższy niż szerokość obrazu (z powodu zaokrąglenia). Dlatego istnieją osobne funkcje do uzyskiwania szerokości i liczby bajtów w wierszu. W tej chwili nie masz tego problemu. Ale to może się zmienić w następnej wersji iOS. Więc Twój kod powinien być:

int bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
int bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
int bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
int size = bufferHeight * bytesPerRow ;

unsigned char *pixel = (unsigned char*)malloc(size);

unsigned char *rowBase = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
memcpy (pixel, rowBase, size);

Należy również pamiętać, że Twój kod będzie nieszczęśliwie nie na iPhone 3G.

 16
Author: Codo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-23 04:55:55

Jeśli potrzebujesz tylko kanału luminancji, odradzam używanie formatu BGRA, ponieważ ma on narzut konwersji. Apple sugeruje użycie BGRA, jeśli robisz renderowanie, ale nie potrzebujesz go do wyodrębniania informacji o luminancji. Jak już wspomniał Brad, najbardziej wydajnym formatem jest natywny dla Kamery format YUV.

Jednak wyodrębnianie właściwych bajtów z bufora próbki jest nieco trudne, szczególnie w przypadku iPhone ' a 3G z przeplatanym formatem YUV 422. Więc Oto Mój kod, który działa dobrze z iPhone 3G, 3GS, iPod Touch 4 i iPhone 4s.

#pragma mark -
#pragma mark AVCaptureVideoDataOutputSampleBufferDelegate Methods
#if !(TARGET_IPHONE_SIMULATOR)
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
{
    // get image buffer reference
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);

    // extract needed informations from image buffer
    CVPixelBufferLockBaseAddress(imageBuffer, 0);
    size_t bufferSize = CVPixelBufferGetDataSize(imageBuffer);
    void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
    CGSize resolution = CGSizeMake(CVPixelBufferGetWidth(imageBuffer), CVPixelBufferGetHeight(imageBuffer));

    // variables for grayscaleBuffer 
    void *grayscaleBuffer = 0;
    size_t grayscaleBufferSize = 0;

    // the pixelFormat differs between iPhone 3G and later models
    OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer);

    if (pixelFormat == '2vuy') { // iPhone 3G
        // kCVPixelFormatType_422YpCbCr8     = '2vuy',    
        /* Component Y'CbCr 8-bit 4:2:2, ordered Cb Y'0 Cr Y'1 */

        // copy every second byte (luminance bytes form Y-channel) to new buffer
        grayscaleBufferSize = bufferSize/2;
        grayscaleBuffer = malloc(grayscaleBufferSize);
        if (grayscaleBuffer == NULL) {
            NSLog(@"ERROR in %@:%@:%d: couldn't allocate memory for grayscaleBuffer!", NSStringFromClass([self class]), NSStringFromSelector(_cmd), __LINE__);
            return nil; }
        memset(grayscaleBuffer, 0, grayscaleBufferSize);
        void *sourceMemPos = baseAddress + 1;
        void *destinationMemPos = grayscaleBuffer;
        void *destinationEnd = grayscaleBuffer + grayscaleBufferSize;
        while (destinationMemPos <= destinationEnd) {
            memcpy(destinationMemPos, sourceMemPos, 1);
            destinationMemPos += 1;
            sourceMemPos += 2;
        }       
    }

    if (pixelFormat == '420v' || pixelFormat == '420f') {
        // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange = '420v', 
        // kCVPixelFormatType_420YpCbCr8BiPlanarFullRange  = '420f',
        // Bi-Planar Component Y'CbCr 8-bit 4:2:0, video-range (luma=[16,235] chroma=[16,240]).  
        // Bi-Planar Component Y'CbCr 8-bit 4:2:0, full-range (luma=[0,255] chroma=[1,255]).
        // baseAddress points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct
        // i.e.: Y-channel in this format is in the first third of the buffer!
        int bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
        baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer,0);
        grayscaleBufferSize = resolution.height * bytesPerRow ;
        grayscaleBuffer = malloc(grayscaleBufferSize);
        if (grayscaleBuffer == NULL) {
            NSLog(@"ERROR in %@:%@:%d: couldn't allocate memory for grayscaleBuffer!", NSStringFromClass([self class]), NSStringFromSelector(_cmd), __LINE__);
            return nil; }
        memset(grayscaleBuffer, 0, grayscaleBufferSize);
        memcpy (grayscaleBuffer, baseAddress, grayscaleBufferSize); 
    }

    // do whatever you want with the grayscale buffer
    ...

    // clean-up
    free(grayscaleBuffer);
}
#endif
 6
Author: Tafkadasoh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-25 11:59:52

Jest to po prostu kulminacja ciężkiej pracy wszystkich innych, powyżej i na innych wątkach, przekonwertowana do swift 3 dla każdego, kto uzna to za przydatne.

func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
    if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
        CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly)

        let pixelFormatType = CVPixelBufferGetPixelFormatType(pixelBuffer)
        if pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
           || pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange {

            let bufferHeight = CVPixelBufferGetHeight(pixelBuffer)
            let bufferWidth = CVPixelBufferGetWidth(pixelBuffer)

            let lumaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)
            let size = bufferHeight * lumaBytesPerRow
            let lumaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)
            let lumaByteBuffer = unsafeBitCast(lumaBaseAddress, to:UnsafeMutablePointer<UInt8>.self)

            let releaseDataCallback: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
                // https://developer.apple.com/reference/coregraphics/cgdataproviderreleasedatacallback
                // N.B. 'CGDataProviderRelease' is unavailable: Core Foundation objects are automatically memory managed
                return
            }

            if let dataProvider = CGDataProvider(dataInfo: nil, data: lumaByteBuffer, size: size, releaseData: releaseDataCallback) {
                let colorSpace = CGColorSpaceCreateDeviceGray()
                let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue)

                let cgImage = CGImage(width: bufferWidth, height: bufferHeight, bitsPerComponent: 8, bitsPerPixel: 8, bytesPerRow: lumaBytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo, provider: dataProvider, decode: nil, shouldInterpolate: false, intent: CGColorRenderingIntent.defaultIntent)

                let greyscaleImage = UIImage(cgImage: cgImage!)
                // do what you want with the greyscale image.
            }
        }

        CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly)
    }
}
 2
Author: Awesomeness,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-27 01:09:35