web-dev-qa-db-fra.com

Comment transformer un CVPixelBuffer en UIImage?

J'ai des problèmes pour obtenir un UIIMage à partir d'un CVPixelBuffer. C'est ce que j'essaye:

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 deux correctement réglés sur la résolution de la caméra. image est créé mais il me semble être noir (ou peut-être transparent?). Je ne comprends pas très bien où est le problème. Toute idée serait appréciée.

32
mahboudz

Tout d’abord, les éléments évidents qui ne se rapportent pas directement à votre question: AVCaptureVideoPreviewLayer est le moyen le moins coûteux d’alimenter une vidéo provenant de l’une ou l’autre des caméras dans une vue indépendante si les données proviennent de cet emplacement et si vous n’avez pas l’intention de modifier immédiatement. il. Vous n'avez pas à vous pousser vous-même, la couche de prévisualisation est directement connectée à la variable AVCaptureSession et se met à jour elle-même.

Je dois admettre que je manque de confiance en la question centrale. Il existe une différence sémantique entre une CIImage et les deux autres types d'image - une CIImage est une recette pour une image et n'est pas nécessairement soutenue par des pixels. Cela peut être quelque chose comme "prendre les pixels à partir 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 à quoi ressemble une CIImage tant que vous n'avez pas choisi de la restituer. Il ne connaît pas non plus les limites appropriées pour la rasteriser.

UIImage prétend simplement envelopper une CIImage. Il ne le convertit pas en pixels. On peut supposer que UIImageView devrait atteindre cet objectif, mais si c'est le cas, je n'arrive pas à trouver où vous fourniriez le rectangle de sortie approprié.

J'ai eu du succès en évitant le problème avec:

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 occasion évidente de spécifier le rectangle de sortie. Je suis sûr qu'il existe une route sans intermédiaire CGImage, donc veuillez ne pas présumer que cette solution est la meilleure pratique.

46
Tommy

Une autre façon d’obtenir un UIImage. Fonctionne environ 10 fois plus vite, du 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();
13
Jonathan Cichon

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

Remarque: cela ne fonctionne que pour les tampons de pixels RVB, pas pour les niveaux de gris. 

13
Andrey M.

À moins que vos données d'image ne soient dans un format différent qui nécessite swizzle ou conversion - je vous recommanderais de ne pas incrémenter quoi que ce soit ... mettez simplement les données dans votre zone de mémoire de contexte 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
joe

Les méthodes précédentes m'avaient conduit à une fuite de données CG Raster. Cette méthode de conversion n'a pas coulé 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
Vlad

Une solution moderne serait

let image = UIImage(ciImage: CIImage(cvPixelBuffer: YOUR_BUFFER))
0
ethamine