iOS – Escala y recorta CMSampleBufferRef / CVImageBufferRef

Estoy utilizando AVFoundation y obtengo el buffer de muestra de AVCaptureVideoDataOutput , puedo escribirlo directamente en videoWriter usando:

 - (void)writeBufferFrame:(CMSampleBufferRef)sampleBuffer { CMTime lastSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); if(self.videoWriter.status != AVAssetWriterStatusWriting) { [self.videoWriter startWriting]; [self.videoWriter startSessionAtSourceTime:lastSampleTime]; } [self.videoWriterInput appendSampleBuffer:sampleBuffer]; } 

Lo que quiero hacer ahora es recortar y escalar la imagen dentro de CMSampleBufferRef sin convertirla en UIImage o CGImageRef porque eso ralentiza el rendimiento.

Si utiliza vimage, puede trabajar directamente en los datos del búfer sin convertirlo a ningún formato de imagen.

outImg contiene los datos de imagen recortada y escalada. La relación entre outWidth y cropWidth establece la escala. recorte vimage

 int cropX0, cropY0, cropHeight, cropWidth, outWidth, outHeight; CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CVPixelBufferLockBaseAddress(imageBuffer,0); void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); vImage_Buffer inBuff; inBuff.height = cropHeight; inBuff.width = cropWidth; inBuff.rowBytes = bytesPerRow; int startpos = cropY0*bytesPerRow+4*cropX0; inBuff.data = baseAddress+startpos; unsigned char *outImg= (unsigned char*)malloc(4*outWidth*outHeight); vImage_Buffer outBuff = {outImg, outHeight, outWidth, 4*outWidth}; vImage_Error err = vImageScale_ARGB8888(&inBuff, &outBuff, NULL, 0); if (err != kvImageNoError) NSLog(@" error %ld", err); 

Por lo tanto, establecer cropX0 = 0 y cropY0 = 0 y cropWidth y cropHeight en el tamaño original significa que no hay recorte (usando toda la imagen original). Setting outWidth = cropWidth y outHeight = cropHeight no produce escalas. Tenga en cuenta que inBuff.rowBytes siempre debe ser la longitud del buffer fuente completo, no la longitud recortada.

Puede considerar usar CoreImage (5.0+).

 CIImage *ciImage = [CIImage imageWithCVPixelBuffer:CMSampleBufferGetImageBuffer(sampleBuffer) options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNull null], kCIImageColorSpace, nil]]; ciImage = [[ciImage imageByApplyingTransform:myScaleTransform] imageByCroppingToRect:myRect]; 

darse cuenta

Recientemente necesité escribir esta función nuevamente y descubrí que este método ya no parece funcionar (al menos no pude hacerlo funcionar en iOS 10.3.1). La imagen de salida no se alinearía. Supongo que es debido a los bytesPerRow incorrectos.


Respuesta original

El búfer es simplemente una matriz de píxeles, por lo que puede procesar el búfer directamente sin usar vImage. El código está escrito en Swift, pero creo que es fácil encontrar el equivalente de Objective-C.

Swift 3

 let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)! CVPixelBufferLockBaseAddress(imageBuffer, .readOnly) let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer) let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer) let cropWidth = 640 let cropHeight = 640 let colorSpace = CGColorSpaceCreateDeviceRGB() let context = CGContext(data: baseAddress, width: cropWidth, height: cropHeight, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue) CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) // create image let cgImage: CGImage = context!.makeImage()! let image = UIImage(cgImage: cgImage) 

Swift 2

 let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)! CVPixelBufferLockBaseAddress(imageBuffer, 0) let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer) let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer) let cropWidth = 640 let cropHeight = 640 let colorSpace = CGColorSpaceCreateDeviceRGB() let context = CGBitmapContextCreate(baseAddress, cropWidth, cropHeight, 8, bytesPerRow, colorSpace, CGImageAlphaInfo.NoneSkipFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue) // create image let cgImage: CGImageRef = CGBitmapContextCreateImage(context)! let image = UIImage(CGImage: cgImage) 

Si desea recortar desde una posición específica, agregue el siguiente código:

 // calculate start position let bytesPerPixel = 4 let startPoint = [ "x": 10, "y": 10 ] let startAddress = baseAddress + startPoint["y"]! * bytesPerRow + startPoint["x"]! * bytesPerPixel 

y cambie baseAddress en CGBitmapContextCreate en startAddress . Asegúrese de no exceder el ancho y alto de la imagen de origen.

Para escalar puede hacer que AVFoundation haga esto por usted. Ver mi publicación reciente aquí . Al establecer el valor de la tecla AVVideoWidth / AVVideoHeight se escalarán las imágenes si no son de las mismas dimensiones. Eche un vistazo a las propiedades aquí . En cuanto al cultivo, no estoy seguro si puede hacer que AVFoundation lo haga por usted. Puede que tenga que recurrir al uso de OpenGL o CoreImage. Hay un par de buenos enlaces en la publicación superior para esta pregunta SO .

Prueba esto en Swift3

 func resize(_ destSize: CGSize)-> CVPixelBuffer? { guard let imageBuffer = CMSampleBufferGetImageBuffer(self) else { return nil } // Lock the image buffer CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0)) // Get information about the image let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer) let bytesPerRow = CGFloat(CVPixelBufferGetBytesPerRow(imageBuffer)) let height = CGFloat(CVPixelBufferGetHeight(imageBuffer)) let width = CGFloat(CVPixelBufferGetWidth(imageBuffer)) var pixelBuffer: CVPixelBuffer? let options = [kCVPixelBufferCGImageCompatibilityKey:true, kCVPixelBufferCGBitmapContextCompatibilityKey:true] let topMargin = (height - destSize.height) / CGFloat(2) let leftMargin = (width - destSize.width) * CGFloat(2) let baseAddressStart = Int(bytesPerRow * topMargin + leftMargin) let addressPoint = baseAddress!.assumingMemoryBound(to: UInt8.self) let status = CVPixelBufferCreateWithBytes(kCFAllocatorDefault, Int(destSize.width), Int(destSize.height), kCVPixelFormatType_32BGRA, &addressPoint[baseAddressStart], Int(bytesPerRow), nil, nil, options as CFDictionary, &pixelBuffer) if (status != 0) { print(status) return nil; } CVPixelBufferUnlockBaseAddress(imageBuffer,CVPixelBufferLockFlags(rawValue: 0)) return pixelBuffer; }