You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 20:47:00