如何直接在IOS 4中旋转CVImageBuffer图像而不转换为UIImage?

Ian*_*nas 11 opencv affinetransform ios

我在iPhone上使用OpenCV 2.2来检测面部.我正在使用IOS 4的AVCaptureSession来访问摄像机流,如下面的代码所示.

我的挑战是视频帧以CVBufferRef(指向CVImageBuffer)对象的形式出现,它们以480px宽,300px高的方式呈现为风景.如果您将手机侧向握住,这很好,但是当手机处于直立位置时,我想将这些框架顺时针旋转90度,以便OpenCV可以正确找到面部.

可以将CVBufferRef转换为CGImage,然后转换为UIImage,然后旋转,就像这个人正在做的那样:旋转从视频帧中获取的CGImage

然而,这浪费了很多CPU.我正在寻找一种更快速旋转图像的方法,如果可能的话,理想情况下使用GPU进行处理.

有任何想法吗?

伊恩

代码示例:

 -(void) startCameraCapture {
  // Start up the face detector

  faceDetector = [[FaceDetector alloc] initWithCascade:@"haarcascade_frontalface_alt2" withFileExtension:@"xml"];

  // Create the AVCapture Session
  session = [[AVCaptureSession alloc] init];

  // create a preview layer to show the output from the camera
  AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:session];
  previewLayer.frame = previewView.frame;
  previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

  [previewView.layer addSublayer:previewLayer];

  // Get the default camera device
  AVCaptureDevice* camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

  // Create a AVCaptureInput with the camera device
  NSError *error=nil;
  AVCaptureInput* cameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:camera error:&error];
  if (cameraInput == nil) {
   NSLog(@"Error to create camera capture:%@",error);
  }

  // Set the output
  AVCaptureVideoDataOutput* videoOutput = [[AVCaptureVideoDataOutput alloc] init];
  videoOutput.alwaysDiscardsLateVideoFrames = YES;

  // create a queue besides the main thread queue to run the capture on
  dispatch_queue_t captureQueue = dispatch_queue_create("catpureQueue", NULL);

  // setup our delegate
  [videoOutput setSampleBufferDelegate:self queue:captureQueue];

  // release the queue.  I still don't entirely understand why we're releasing it here,
  // but the code examples I've found indicate this is the right thing.  Hmm...
  dispatch_release(captureQueue);

  // configure the pixel format
  videoOutput.videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
          [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA], 
          (id)kCVPixelBufferPixelFormatTypeKey,
          nil];

  // and the size of the frames we want
  // try AVCaptureSessionPresetLow if this is too slow...
  [session setSessionPreset:AVCaptureSessionPresetMedium];

  // If you wish to cap the frame rate to a known value, such as 10 fps, set 
  // minFrameDuration.
  videoOutput.minFrameDuration = CMTimeMake(1, 10);

  // Add the input and output
  [session addInput:cameraInput];
  [session addOutput:videoOutput];

  // Start the session
  [session startRunning];  
 }

 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
  // only run if we're not already processing an image
  if (!faceDetector.imageNeedsProcessing) {

   // Get CVImage from sample buffer
   CVImageBufferRef cvImage = CMSampleBufferGetImageBuffer(sampleBuffer);

   // Send the CVImage to the FaceDetector for later processing
   [faceDetector setImageFromCVPixelBufferRef:cvImage];

   // Trigger the image processing on the main thread
   [self performSelectorOnMainThread:@selector(processImage) withObject:nil waitUntilDone:NO];
  }
 }
Run Code Online (Sandbox Code Playgroud)

Ste*_*ten 16

vImage是一种非常快速的方法.但需要ios5.呼叫说ARGB,但它适用于从缓冲区获得的BGRA.

这也有一个优点,你可以切出缓冲区的一部分并旋转它.在这里看到我的答案

- (unsigned char*) rotateBuffer: (CMSampleBufferRef) sampleBuffer
{
 CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
 CVPixelBufferLockBaseAddress(imageBuffer,0);

 size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
 size_t width = CVPixelBufferGetWidth(imageBuffer);
 size_t height = CVPixelBufferGetHeight(imageBuffer);
 size_t currSize = bytesPerRow*height*sizeof(unsigned char); 
 size_t bytesPerRowOut = 4*height*sizeof(unsigned char); 

 void *srcBuff = CVPixelBufferGetBaseAddress(imageBuffer); 
 unsigned char *outBuff = (unsigned char*)malloc(currSize);  

 vImage_Buffer ibuff = { srcBuff, height, width, bytesPerRow};
 vImage_Buffer ubuff = { outBuff, width, height, bytesPerRowOut};

 uint8_t rotConst = 1;   // 0, 1, 2, 3 is equal to 0, 90, 180, 270 degrees rotation

 vImage_Error err= vImageRotate90_ARGB8888 (&ibuff, &ubuff, NULL, rotConst, NULL,0);
 if (err != kvImageNoError) NSLog(@"%ld", err);

 return outBuff;
}
Run Code Online (Sandbox Code Playgroud)

  • 在将视频写入文件之前,我使用类似的东西来操纵视频的各个sampleBuffer帧.有几点需要注意:`vImageRotate ...`函数原型已经改变,我的调用看起来像`vImageRotate90_ARGB8888(&inbuff,&outbuff,rotationConstant,bgColor,0);`(其中`uint8_t bgColor [4] = {0,0 ,0,0};`).而且你必须手动创建一个`CVPixelBufferRef`,以便将生成的图像数据传递给`AVAssetWriterInputPixelBufferAdaptor`.只是不要忘记创建一个`CVPixelBufferReleaseBytesCallback`来释放此函数中的malloc-ed数据缓冲区. (3认同)

Ste*_*lin 3

如果您旋转 90 度停止,那么您可以在内存中执行此操作。下面的示例代码只是将数据复制到新的像素缓冲区。进行强力旋转应该是直接的。

- (CVPixelBufferRef) rotateBuffer: (CMSampleBufferRef) sampleBuffer
{
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CVPixelBufferLockBaseAddress(imageBuffer,0);

    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
    size_t width = CVPixelBufferGetWidth(imageBuffer);
    size_t height = CVPixelBufferGetHeight(imageBuffer);

    void *src_buff = CVPixelBufferGetBaseAddress(imageBuffer);

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
                             [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
                             nil];

    CVPixelBufferRef pxbuffer = NULL;
    //CVReturn status = CVPixelBufferPoolCreatePixelBuffer (NULL, _pixelWriter.pixelBufferPool, &pxbuffer);
    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, width,
                                          height, kCVPixelFormatType_32BGRA, (CFDictionaryRef) options, 
                                          &pxbuffer);

    NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);

    CVPixelBufferLockBaseAddress(pxbuffer, 0);
    void *dest_buff = CVPixelBufferGetBaseAddress(pxbuffer);
    NSParameterAssert(dest_buff != NULL);

    int *src = (int*) src_buff ;
    int *dest= (int*) dest_buff ;
    size_t count = (bytesPerRow * height) / 4 ;
    while (count--) {
        *dest++ = *src++;
    }

    //Test straight copy.
    //memcpy(pxdata, baseAddress, width * height * 4) ;
    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
    CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
    return pxbuffer;
}
Run Code Online (Sandbox Code Playgroud)

如果您要将其写回 AVAssetWriterInput,则可以使用 AVAssetWriterInputPixelBufferAdaptor。

以上未优化。您可能想要寻找更有效的复制算法。一个好的起点是In-place Matrix Transpose。您还可能希望使用像素缓冲池,而不是每次都创建一个新的。

编辑。您可以使用 GPU 来完成此操作。这听起来像是大量数据被推送。在 CVPixelBufferRef 中有关键 kCVPixelBufferOpenGLCompatibilityKey。我假设您可以从 CVImageBufferRef (这只是一个像素缓冲区引用)创建一个 OpenGL 兼容图像,并将其推送到着色器。再说一次,在我看来,太过分了。您可能会看到 BLAS 或 LAPACK 是否具有“不适当的”转置方法。如果他们这样做了,那么你可以放心,他们是高度优化的。

90 CW 其中 new_width = width ... 这将为您提供纵向图像。

for (int i = 1; i <= new_height; i++) {
    for (int j = new_width - 1; j > -1; j--) {
        *dest++ = *(src + (j * width) + i) ;
    }
}
Run Code Online (Sandbox Code Playgroud)