58 votes

Comment transformer un CVPixelBuffer en UIImage ?

J'ai des problèmes pour obtenir un UIImage d'un CVPixelBuffer . C'est ce que j'essaie de faire :

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 y width sont toutes deux correctement réglées sur la résolution de l'appareil photo. image est créé mais il semble être noir (ou peut-être transparent ?). Je n'arrive pas à comprendre où se situe le problème. Toute idée serait la bienvenue.

62voto

Tommy Points 56749

Tout d'abord, les choses évidentes qui ne sont pas directement liées à votre question : AVCaptureVideoPreviewLayer est le moyen le plus économique de transférer la vidéo de l'une ou l'autre des caméras dans une vue indépendante si c'est de là que proviennent les données et que vous n'avez pas l'intention de les modifier dans l'immédiat. Vous n'avez rien à faire vous-même, la couche de prévisualisation est directement connectée à l'interface de la caméra. AVCaptureSession et se met à jour.

Je dois admettre que je manque de confiance dans la question centrale. Il y a une différence sémantique entre une CIImage et les deux autres types d'images - une CIImage est une recette pour une image et n'est pas nécessairement soutenue par des pixels. Cela peut être quelque chose comme "prenez les pixels d'ici, transformez comme ceci, appliquez ce filtre, transformez comme ceci, fusionnez avec cette autre image, appliquez ce filtre". Le système ne sait pas ce qu'est une CIImage ressemble à ce que vous avez choisi de rendre. Il ne connaît pas non plus, de manière inhérente, les limites appropriées dans lesquelles il doit être rendu.

UIImage vise simplement à envelopper un CIImage . Il ne le convertit pas en pixels. Vraisemblablement UIImageView devrait y parvenir, mais si c'est le cas, je n'arrive pas à trouver où vous devez fournir le rectangle de sortie approprié.

J'ai eu du succès en contournant 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 y a un moyen de passer sans utiliser une balise CGImage en tant qu'intermédiaire. N'allez donc pas croire que cette solution est la meilleure.

58voto

Andrey M. Points 1080

Essayez celui-ci en Swift.

Swift 4.2 :

import VideoToolbox

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

        guard let cgImage = cgImage else {
            return nil
        }

        self.init(cgImage: cgImage)
    }
}

Swift 5 :

import VideoToolbox

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

        guard let cgImage = cgImage else {
            return nil
        } 

        self.init(cgImage: cgImage)
    }
}

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

12voto

Jonathan Cichon Points 3525

Une autre façon d'obtenir une UIImage. Elle est environ 10 fois plus rapide, 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();

11voto

ethamine Points 159

Une solution moderne serait

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

7voto

joe Points 291

À moins que vos données d'image ne soient dans un format différent qui nécessite une conversion, je vous recommande de ne pas incrémenter quoi que ce soit... envoyez simplement les données dans votre zone de mémoire contextuelle avec memcpy :

//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...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X