2013-05-20 48 views
1

我還在學習AVFoundation,所以我不確定如何最好地處理需要捕捉高質量靜止圖像的問題,但提供的是低質量的預覽視頻流。保存高質量圖像,進行實時處理 - 最佳方法是什麼?

我有一個需要拍攝高質量圖像的應用程序(AVCaptureSessionPresetPhoto),但是使用OpenCV處理預覽視頻流 - 可以接受更低的分辨率。簡單地使用基地OpenCV Video Camera class是不好的,因爲將defaultAVCaptureSessionPreset設置爲AVCaptureSessionPresetPhoto導致全分辨率幀被傳遞到processImage - 這確實非常慢。

如何獲得可用於捕獲靜止圖像的設備的高質量連接以及可以處理和顯示的低質量連接?我需要如何設置會話/連接的說明會非常有幫助。有這樣一個應用程序的開源示例嗎?

+2

如果您要求如何獲得較小的預覽圖像,同時仍然可以抓取靜態照片,則可以將AVCaptureStillImageOutput和AVCaptureVideoDataOutput添加到捕獲會話中(如果定位iOS 4.3+)。對於正常的視頻播放,您將獲得較小的預覽幀,然後當您觸發照片捕獲方法時,將切換到完整的照片分辨率。這就是我在我的GPUImage框架內部執行的操作:https://github.com/BradLarson/GPUImage,您可以查看SimplePhotoFilter示例應用程序以查看這個實例。 –

回答

1

我做了類似的事情 - 我抓住了委託方法中的像素,製作了它們的CGImageRef,然後將它們分派到正常優先級隊列中,並在那裏進行修改。由於AVFoundation必須爲回調方法使用CADisplayLink,因此它具有最高的優先級。在我的特殊情況下,我沒有抓住所有像素,因此它在30fps的iPhone 4上工作。根據你想要運行的設備,你有像素數,fps等等的權衡。

另一個想法是獲取2個像素子集的冪 - 例如每行和每第四行每隔4個。我再次在20-30fps的應用中做了類似的事情。然後,您可以在派發的塊中進一步操作這個較小的圖像。

如果這看起來令人望而生畏,爲工作代碼提供賞金。

CODE:

// Image is oriented with bottle neck to the left and the bottle bottom on the right 
- (void)captureOutput:(AVCaptureVideoDataOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection 
{ 
#if 1 
    AVCaptureDevice *camera = [(AVCaptureDeviceInput *)[captureSession.inputs lastObject] device]; 
    if(camera.adjustingWhiteBalance || camera.adjustingExposure) NSLog(@"GOTCHA: %d %d", camera.adjustingWhiteBalance, camera.adjustingExposure); 
    printf("foo\n"); 
#endif 

    if(saveState != saveOne && saveState != saveAll) return; 


    @autoreleasepool { 
     CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
     //NSLog(@"PE: value=%lld timeScale=%d flags=%x", prStamp.value, prStamp.timescale, prStamp.flags); 

     /*Lock the image buffer*/ 
     CVPixelBufferLockBaseAddress(imageBuffer,0); 

     NSRange captureRange; 
     if(saveState == saveOne) { 
#if 0 // B G R A MODE ! 
NSLog(@"PIXEL_TYPE: 0x%lx", CVPixelBufferGetPixelFormatType(imageBuffer)); 
uint8_t *newPtr = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer); 
NSLog(@"ONE VAL %x %x %x %x", newPtr[0], newPtr[1], newPtr[2], newPtr[3]); 
} 
exit(0); 
#endif 
      [edgeFinder setupImageBuffer:imageBuffer]; 

      BOOL success = [edgeFinder delineate:1]; 

      if(!success) { 
       dispatch_async(dispatch_get_main_queue(), ^{ edgeFinder = nil; [delegate error]; }); 
       saveState = saveNone; 
      } else 
       bottleRange = edgeFinder.sides; 
       xRange.location = edgeFinder.shoulder; 
       xRange.length = edgeFinder.bottom - xRange.location; 

       NSLog(@"bottleRange 1: %@ neck=%d bottom=%d", NSStringFromRange(bottleRange), edgeFinder.shoulder, edgeFinder.bottom); 
       //searchRows = [edgeFinder expandRange:bottleRange]; 

       rowsPerSwath = lrintf((bottleRange.length*NUM_DEGREES_TO_GRAB)*(float)M_PI/360.0f); 
NSLog(@"rowsPerSwath = %d", rowsPerSwath); 
       saveState = saveIdling; 

       captureRange = NSMakeRange(0, [WLIPBase numRows]); 
       dispatch_async(dispatch_get_main_queue(),^
        { 
         [delegate focusDone]; 
         edgeFinder = nil; 
         captureOutput.alwaysDiscardsLateVideoFrames = YES; 
        }); 
     } else {   
      NSInteger rows = rowsPerSwath; 
      NSInteger newOffset = bottleRange.length - rows; 
      if(newOffset & 1) { 
       --newOffset; 
       ++rows; 
      } 
      captureRange = NSMakeRange(bottleRange.location + newOffset/2, rows); 
     } 
     //NSLog(@"captureRange=%u %u", captureRange.location, captureRange.length); 

     /*Get information about the image*/ 
     uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer); 
     size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 
     size_t width = CVPixelBufferGetWidth(imageBuffer); 

     // Note Apple sample code cheats big time - the phone is big endian so this reverses the "apparent" order of bytes 
     CGContextRef newContext = CGBitmapContextCreate(NULL, width, captureRange.length, 8, bytesPerRow, colorSpace, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little); // Video in ARGB format 

assert(newContext); 

     uint8_t *newPtr = (uint8_t *)CGBitmapContextGetData(newContext); 
     size_t offset = captureRange.location * bytesPerRow; 

     memcpy(newPtr, baseAddress + offset, captureRange.length * bytesPerRow); 

     CVPixelBufferUnlockBaseAddress(imageBuffer, 0); 

     OSAtomicIncrement32(&totalImages); 
     int32_t curDepth = OSAtomicIncrement32(&queueDepth); 
     if(curDepth > maxDepth) maxDepth = curDepth; 

#define kImageContext @"kImageContext" 
#define kState   @"kState" 
#define kPresTime  @"kPresTime" 

     CMTime prStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);  // when it was taken? 
     //CMTime deStamp = CMSampleBufferGetDecodeTimeStamp(sampleBuffer);   // now? 

     NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: 
      [NSValue valueWithBytes:&saveState objCType:@encode(saveImages)], kState, 
      [NSValue valueWithNonretainedObject:(__bridge id)newContext], kImageContext, 
      [NSValue valueWithBytes:&prStamp objCType:@encode(CMTime)], kPresTime, 
      nil ]; 
     dispatch_async(imageQueue,^
      { 
       // could be on any thread now 
       OSAtomicDecrement32(&queueDepth); 

       if(!isCancelled) { 
        saveImages state; [(NSValue *)[dict objectForKey:kState] getValue:&state]; 
        CGContextRef context; [(NSValue *)[dict objectForKey:kImageContext] getValue:&context]; 
        CMTime stamp; [(NSValue *)[dict objectForKey:kPresTime] getValue:&stamp]; 

        CGImageRef newImageRef = CGBitmapContextCreateImage(context); 
        CGContextRelease(context); 
        UIImageOrientation orient = state == saveOne ? UIImageOrientationLeft : UIImageOrientationUp; 
        UIImage *image = [UIImage imageWithCGImage:newImageRef scale:1.0 orientation:orient]; // imageWithCGImage: UIImageOrientationUp UIImageOrientationLeft 
        CGImageRelease(newImageRef); 
        NSData *data = UIImagePNGRepresentation(image); 

        // NSLog(@"STATE:[%d]: value=%lld timeScale=%d flags=%x", state, stamp.value, stamp.timescale, stamp.flags); 

        { 
         NSString *name = [NSString stringWithFormat:@"%d.png", num]; 
         NSString *path = [[wlAppDelegate snippetsDirectory] stringByAppendingPathComponent:name]; 
         BOOL ret = [data writeToFile:path atomically:NO]; 
//NSLog(@"WROTE %d err=%d w/time %f path:%@", num, ret, (double)stamp.value/(double)stamp.timescale, path); 
         if(!ret) { 
          ++errors; 
         } else { 
          dispatch_async(dispatch_get_main_queue(),^
           { 
            if(num) [delegate progress:(CGFloat)num/(CGFloat)(MORE_THAN_ONE_REV * SNAPS_PER_SEC) file:path]; 
           }); 
         } 
         ++num; 
        } 
       } else NSLog(@"CANCELLED"); 

      }); 
    } 
} 
+0

我認爲這是通過重寫'captureOutput'來實現的,這只是一個改變圖像生成方式的問題。 – fredley

+0

在ipad上這樣做現在沒有代碼方便。這聽起來像方法名稱。你可以訪問像素,並有一些時間來複制它們。抓取一小部分的速度很快 - 相對而言,執行vImage大小縮減將會非常緩慢。使用這個分數,你可以減少4次內存使用量。然後,如果你真的需要,你可以進一步減少質量函數,但是我會猜測兩個函數的強大可能就是你所需要的。 –

+0

這兩個聲音的聲音很棒,目前使用'CMSampleBufferGetImageBuffer'獲取'CVImageBufferRef',你知道是否有這個函數的等價物允許我指定一個樣本大小(類似於你可以傳遞給'BitmapFactory的'inSampleSize' '在Android中)? – fredley

0

在AVCaptureSessionPresetPhoto用它的小視頻預覽(爲iPhone6約1000x700)高分辨率照片(約3000x2000汽車)。

因此,我使用修改的'CvPhotoCamera'類來處理小預覽並拍攝全尺寸圖片。我在此處發佈此代碼:https://stackoverflow.com/a/31478505/1994445

相關問題