如何修复420f格式CVPixelBufferRef旋转缩放时出现的绿条问题
问题分析与修复方案
出现绿条的核心原因是旋转方向下的尺寸计算错误、CbCr缓冲区的内存/行字节配置错误,以及旋转背景色参数不正确,导致YUV颜色通道错位或填充色偏色。以下是具体修复步骤:
1. 修正Left/Right方向的缩放尺寸计算
当处理Left/Right方向时,原图像旋转后的实际显示宽高是原高和原宽的交换,因此计算缩放比例时需基于旋转后的宽高(原图像的高为宽,原宽为高),否则会导致缩放后的尺寸与目标缓冲区不匹配,引发颜色错位。
替换原代码中Left/Right分支的尺寸计算逻辑:
else if (orientation == kCGImagePropertyOrientationLeft || orientation == kCGImagePropertyOrientationRight) { // 旋转后原图像的宽为原高,高为原宽 size_t rotatedSrcWidth = srcHeight; size_t rotatedSrcHeight = srcWidth; BOOL rotatedSrcIsWider = (CGFloat)rotatedSrcWidth / rotatedSrcHeight > (CGFloat)dstWidth / dstHeight; if (rotatedSrcIsWider) { scaledWidth = dstWidth; scaledHeight = (size_t)round((CGFloat)dstWidth * rotatedSrcHeight / rotatedSrcWidth); } else { scaledHeight = dstHeight; scaledWidth = (size_t)round((CGFloat)dstHeight * rotatedSrcWidth / rotatedSrcHeight); } rotationConstant = (orientation == kCGImagePropertyOrientationLeft) ? kRotate90DegreesClockwise : kRotate90DegreesCounterClockwise; }
2. 修正CbCr中间缓冲区的内存与行字节配置
CbCr平面是交错格式(每个像素包含Cb、Cr各8位,共16位),因此:
- 内存分配需按
(缩放后Y宽/2) * (缩放后Y高/2) * 2计算,而非原代码的scaledWidth*scaledHeight - 行字节
rowBytes需设置为(缩放后CbCr宽) * 2,确保每个行的字节数与像素格式匹配
替换原CbCr中间缓冲区的创建代码:
size_t cbCrIntermediateWidth = scaledWidth / 2; size_t cbCrIntermediateHeight = scaledHeight / 2; void *dstCbCrIntermediateBufferData = malloc(cbCrIntermediateWidth * cbCrIntermediateHeight * 2); vImage_Buffer dstCbCrIntermediateBuffer = { .data = dstCbCrIntermediateBufferData, .width = cbCrIntermediateWidth, .height = cbCrIntermediateHeight, .rowBytes = cbCrIntermediateWidth * 2 };
3. 修正CbCr旋转的背景色参数
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange格式中,Cb/Cr的中性灰值为128,对应16位交错格式的数值是0x8080(Cb=128,Cr=128),原代码使用127会导致填充色偏色,替换为:
error = vImageRotate90_Planar16U(&dstCbCrIntermediateBuffer, &dstCbCrBuffer, rotationConstant, 0x8080, kvImageBackgroundColorFill);
完整修正后的代码
+ (CVPixelBufferRef)resizeProportionallyBuffer:(CVPixelBufferRef)srcPixelBuffer withOrientation:(CGImagePropertyOrientation)orientation toSize:(CGSize)dstSize { OSType pixelFormat = CVPixelBufferGetPixelFormatType(srcPixelBuffer); CVPixelBufferLockFlags srcFlags = kCVPixelBufferLock_ReadOnly; CVPixelBufferLockBaseAddress(srcPixelBuffer, srcFlags); void *srcLuminanceData = CVPixelBufferGetBaseAddressOfPlane(srcPixelBuffer, 0); size_t srcLuminanceWidth = CVPixelBufferGetWidthOfPlane(srcPixelBuffer, 0); size_t srcLuminanceHeight = CVPixelBufferGetHeightOfPlane(srcPixelBuffer, 0); size_t srcLuminanceBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(srcPixelBuffer, 0); void *srcCbCrData = CVPixelBufferGetBaseAddressOfPlane(srcPixelBuffer, 1); int srcCbCrWidth = (int)CVPixelBufferGetWidthOfPlane(srcPixelBuffer, 1); int srcCbCrHeight = (int)CVPixelBufferGetHeightOfPlane(srcPixelBuffer, 1); size_t srcCbCrBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(srcPixelBuffer, 1); vImage_Buffer srcLuminanceBuffer = { .data = srcLuminanceData, .width = srcLuminanceWidth, .height = srcLuminanceHeight, .rowBytes = srcLuminanceBytesPerRow }; vImage_Buffer srcCbCrBuffer = { .data = srcCbCrData, .width = srcCbCrWidth, .height = srcCbCrHeight, .rowBytes = srcCbCrBytesPerRow }; size_t srcWidth = srcLuminanceWidth; size_t srcHeight = srcLuminanceHeight; int dstWidth = (int)dstSize.width; int dstHeight = (int)dstSize.height; uint8_t rotationConstant = 0; size_t scaledWidth = srcLuminanceWidth; size_t scaledHeight = srcLuminanceHeight; if (orientation == kCGImagePropertyOrientationUp || orientation == kCGImagePropertyOrientationDown) { BOOL srcIsWider = dstHeight * srcWidth > dstWidth * srcHeight; scaledWidth = srcIsWider ? dstWidth : (size_t)round((CGFloat)(dstHeight * srcWidth) / (CGFloat)srcHeight); scaledHeight = srcIsWider ? (size_t)round((CGFloat)(dstWidth * srcHeight) / (CGFloat)srcWidth) : dstHeight; rotationConstant = (orientation == kCGImagePropertyOrientationUp) ? kRotate0DegreesClockwise : kRotate180DegreesClockwise; } else if (orientation == kCGImagePropertyOrientationLeft || orientation == kCGImagePropertyOrientationRight) { // 修正:基于旋转后的宽高计算缩放尺寸 size_t rotatedSrcWidth = srcHeight; size_t rotatedSrcHeight = srcWidth; BOOL rotatedSrcIsWider = (CGFloat)rotatedSrcWidth / rotatedSrcHeight > (CGFloat)dstWidth / dstHeight; if (rotatedSrcIsWider) { scaledWidth = dstWidth; scaledHeight = (size_t)round((CGFloat)dstWidth * rotatedSrcHeight / rotatedSrcWidth); } else { scaledHeight = dstHeight; scaledWidth = (size_t)round((CGFloat)dstHeight * rotatedSrcWidth / rotatedSrcHeight); } rotationConstant = (orientation == kCGImagePropertyOrientationLeft) ? kRotate90DegreesClockwise : kRotate90DegreesCounterClockwise; } Pixel_8 backColor = 0; CVPixelBufferRef dstPixelBuffer = NULL; CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault, dstWidth, dstHeight, pixelFormat, nil, &dstPixelBuffer); if (result != kCVReturnSuccess) { CVPixelBufferUnlockBaseAddress(srcPixelBuffer, srcFlags); return NULL; } CVPixelBufferLockBaseAddress(dstPixelBuffer, 0); void *dstLuminanceData = CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 0); int dstLuminanceWidth = (int)CVPixelBufferGetWidthOfPlane(dstPixelBuffer, 0); int dstLuminanceHeight = (int)CVPixelBufferGetHeightOfPlane(dstPixelBuffer, 0); size_t dstLuminanceBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(dstPixelBuffer, 0); void *dstCbCrData = CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 1); int dstCbCrWidth = (int)CVPixelBufferGetWidthOfPlane(dstPixelBuffer, 1); int dstCbCrHeight = (int)CVPixelBufferGetHeightOfPlane(dstPixelBuffer, 1); size_t dstCbCrBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(dstPixelBuffer, 1); vImage_Buffer dstLuminanceBuffer = { .data = dstLuminanceData, .width = dstLuminanceWidth, .height = dstLuminanceHeight, .rowBytes = dstLuminanceBytesPerRow }; vImage_Buffer dstCbCrBuffer = { .data = dstCbCrData, .width = dstCbCrWidth, .height = dstCbCrHeight, .rowBytes = dstCbCrBytesPerRow }; // Y平面处理 void *dstLuminanceIntermediateBufferData = malloc(scaledWidth*scaledHeight); vImage_Buffer dstLuminanceIntermediateBuffer = { .data = dstLuminanceIntermediateBufferData, .width = scaledWidth, .height = scaledHeight, .rowBytes = scaledWidth }; vImage_Error error = vImageScale_Planar8(&srcLuminanceBuffer, &dstLuminanceIntermediateBuffer, nil, kvImageNoFlags); error = vImageRotate90_Planar8(&dstLuminanceIntermediateBuffer, &dstLuminanceBuffer, rotationConstant, backColor, kvImageBackgroundColorFill); free(dstLuminanceIntermediateBufferData); // 修正:CbCr平面处理 size_t cbCrIntermediateWidth = scaledWidth / 2; size_t cbCrIntermediateHeight = scaledHeight / 2; void *dstCbCrIntermediateBufferData = malloc(cbCrIntermediateWidth * cbCrIntermediateHeight * 2); vImage_Buffer dstCbCrIntermediateBuffer = { .data = dstCbCrIntermediateBufferData, .width = cbCrIntermediateWidth, .height = cbCrIntermediateHeight, .rowBytes = cbCrIntermediateWidth * 2 }; error = vImageScale_CbCr8(&srcCbCrBuffer, &dstCbCrIntermediateBuffer, nil, kvImageNoFlags); // 修正:背景色使用中性灰0x8080 error = vImageRotate90_Planar16U(&dstCbCrIntermediateBuffer, &dstCbCrBuffer, rotationConstant, 0x8080, kvImageBackgroundColorFill); free(dstCbCrIntermediateBufferData); CVPixelBufferUnlockBaseAddress(srcPixelBuffer, srcFlags); CVPixelBufferUnlockBaseAddress(dstPixelBuffer, 0); return dstPixelBuffer; }
内容的提问来源于stack exchange,提问作者Viktoriia
相关产品推荐
相关产品推荐

