Comment transformer un CVPixelBuffer en UIImage?

j'ai du mal à obtenir une image D'un CVPixelBuffer. C'est ce que j'essaie:

CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(imageDataSampleBuffer);
CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, imageDataSampleBuffer, kCMAttachmentMode_ShouldPropagate);
CIImage *ciImage = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer options:(NSDictionary *)attachments];
if (attachments)
    CFRelease(attachments);
size_t width = CVPixelBufferGetWidth(pixelBuffer);
size_t height = CVPixelBufferGetHeight(pixelBuffer);
if (width && height) { // test to make sure we have valid dimensions
    UIImage *image = [[UIImage alloc] initWithCIImage:ciImage];

    UIImageView *lv = [[UIImageView alloc] initWithFrame:self.view.frame];
    lv.contentMode = UIViewContentModeScaleAspectFill;
    self.lockedView = lv;
    [lv release];
    self.lockedView.image = image;
    [image release];
}
[ciImage release];

height et width sont tous les deux correctement positionnés à la résolution de la caméra. image est créé mais il semble être noir (ou peut-être transparent?). Je ne comprends pas très bien où est le problème. Toutes les idées seront les bienvenues.

27
demandé sur mahboudz 2011-11-10 01:50:46

5 réponses

tout D'abord les choses évidentes qui ne se rapportent pas directement à votre question:AVCaptureVideoPreviewLayer est le moyen le moins cher de transférer la vidéo de l'une ou l'autre des caméras vers une vue indépendante si c'est d'où viennent les données et que vous n'avez pas l'intention de les modifier immédiatement. Vous n'avez pas besoin de pousser vous-même, le calque de prévisualisation est directement connecté au AVCaptureSession et mises à jour lui-même.

je dois admettre que je manque de confiance dans la question centrale. Il y a une sémantique la différence entre un CIImage et les deux autres types d'image - un CIImage est une recette pour une image et n'est pas nécessairement soutenus par des pixels. Il peut être quelque chose comme"prendre les pixels d'ici, transformer comme ceci, appliquer ce filtre, transformer comme ceci, fusionner avec cette autre image, appliquer ce filtre". Le système ne sait pas ce qu'est un CIImage ressemble jusqu'à ce que vous choisissiez de le rendre. Il ne connaît pas non plus, par nature, les limites appropriées pour le rasteriser.

UIImage ne prétend qu'envelopper un CIImage. Il ne le convertit pas en pixels. Vraisemblablement UIImageView devrait réaliser cela, mais si c'est le cas, je ne peux pas trouver où vous fourniriez le rectangle de sortie approprié.

j'ai eu du succès juste en esquivant autour de la question:

CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];

CIContext *temporaryContext = [CIContext contextWithOptions:nil];
CGImageRef videoImage = [temporaryContext
                   createCGImage:ciImage
                   fromRect:CGRectMake(0, 0, 
                          CVPixelBufferGetWidth(pixelBuffer),
                          CVPixelBufferGetHeight(pixelBuffer))];

UIImage *uiImage = [UIImage imageWithCGImage:videoImage];
CGImageRelease(videoImage);

Avec donne une possibilité évidente de spécifier le rectangle de sortie. Je suis sûr qu'il y a une route à travers sans utiliser un CGImage en tant qu'intermédiaire, alors ne présumez pas que cette solution est la meilleure. pratique.

40
répondu Tommy 2015-03-11 23:40:13

une autre façon d'obtenir un UIImage. Effectue ~10 fois plus vite, au moins dans mon cas:

int w = CVPixelBufferGetWidth(pixelBuffer);
int h = CVPixelBufferGetHeight(pixelBuffer);
int r = CVPixelBufferGetBytesPerRow(pixelBuffer);
int bytesPerPixel = r/w;

unsigned char *buffer = CVPixelBufferGetBaseAddress(pixelBuffer);

UIGraphicsBeginImageContext(CGSizeMake(w, h));

CGContextRef c = UIGraphicsGetCurrentContext();

unsigned char* data = CGBitmapContextGetData(c);
if (data != NULL) {
   int maxY = h;
   for(int y = 0; y<maxY; y++) {
      for(int x = 0; x<w; x++) {
         int offset = bytesPerPixel*((w*y)+x);
         data[offset] = buffer[offset];     // R
         data[offset+1] = buffer[offset+1]; // G
         data[offset+2] = buffer[offset+2]; // B
         data[offset+3] = buffer[offset+3]; // A
      }
   }
} 
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();
12
répondu Jonathan Cichon 2012-04-26 16:33:04

sauf si vos données d'image sont dans un format différent qui nécessite swizzle ou la conversion - Je ne recommande aucune incrémentation de quoi que ce soit... tapez simplement les données dans votre zone de mémoire contextuelle avec memcpy comme dans:

//not here... unsigned char *buffer = CVPixelBufferGetBaseAddress(pixelBuffer);

UIGraphicsBeginImageContext(CGSizeMake(w, h));

CGContextRef c = UIGraphicsGetCurrentContext();

void *ctxData = CGBitmapContextGetData(c);

// MUST READ-WRITE LOCK THE PIXEL BUFFER!!!!
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
void *pxData = CVPixelBufferGetBaseAddress(pixelBuffer);
memcpy(ctxData, pxData, 4 * w * h);
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);

... and so on...
8
répondu joe 2013-05-16 13:37:03

essayez celui-ci dans Swift.

extension UIImage {
    public convenience init?(pixelBuffer: CVPixelBuffer) {
        var cgImage: CGImage?
        VTCreateCGImageFromCVPixelBuffer(pixelBuffer, nil, &cgImage)

        if let cgImage = cgImage {
            self.init(cgImage: cgImage)
        } else {
            return nil
        }
    }
}

Note: cela ne fonctionne que pour les tampons de pixels RGB, pas pour l'échelle de gris.

7
répondu Andrey M. 2018-01-29 03:26:32

les méthodes précédentes m'ont amené à avoir des fuites de données de grille-pain. Cette méthode de conversion ne fuit pas pour moi:

@autoreleasepool {

    CGImageRef cgImage = NULL;
    OSStatus res = CreateCGImageFromCVPixelBuffer(pixelBuffer,&cgImage);
    if (res == noErr){
        UIImage *image= [UIImage imageWithCGImage:cgImage scale:1.0 orientation:UIImageOrientationUp];

    }
    CGImageRelease(cgImage);
}


    static OSStatus CreateCGImageFromCVPixelBuffer(CVPixelBufferRef pixelBuffer, CGImageRef *imageOut)
    {
        OSStatus err = noErr;
        OSType sourcePixelFormat;
        size_t width, height, sourceRowBytes;
        void *sourceBaseAddr = NULL;
        CGBitmapInfo bitmapInfo;
        CGColorSpaceRef colorspace = NULL;
        CGDataProviderRef provider = NULL;
        CGImageRef image = NULL;

        sourcePixelFormat = CVPixelBufferGetPixelFormatType( pixelBuffer );
        if ( kCVPixelFormatType_32ARGB == sourcePixelFormat )
            bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipFirst;
        else if ( kCVPixelFormatType_32BGRA == sourcePixelFormat )
            bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst;
        else
            return -95014; // only uncompressed pixel formats

        sourceRowBytes = CVPixelBufferGetBytesPerRow( pixelBuffer );
        width = CVPixelBufferGetWidth( pixelBuffer );
        height = CVPixelBufferGetHeight( pixelBuffer );

        CVPixelBufferLockBaseAddress( pixelBuffer, 0 );
        sourceBaseAddr = CVPixelBufferGetBaseAddress( pixelBuffer );

        colorspace = CGColorSpaceCreateDeviceRGB();

        CVPixelBufferRetain( pixelBuffer );
        provider = CGDataProviderCreateWithData( (void *)pixelBuffer, sourceBaseAddr, sourceRowBytes * height, ReleaseCVPixelBuffer);
        image = CGImageCreate(width, height, 8, 32, sourceRowBytes, colorspace, bitmapInfo, provider, NULL, true, kCGRenderingIntentDefault);

        if ( err && image ) {
            CGImageRelease( image );
            image = NULL;
        }
        if ( provider ) CGDataProviderRelease( provider );
        if ( colorspace ) CGColorSpaceRelease( colorspace );
        *imageOut = image;
        return err;
    }

    static void ReleaseCVPixelBuffer(void *pixel, const void *data, size_t size)
    {
        CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)pixel;
        CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 );
        CVPixelBufferRelease( pixelBuffer );
    }
3
répondu Vlad 2014-05-15 06:43:13