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.
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.
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();
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.
À 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...
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 );
}
Une solution moderne serait
let image = UIImage(ciImage: CIImage(cvPixelBuffer: YOUR_BUFFER))