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