【问题标题】:Create RGB image from each channel从每个通道创建 RGB 图像
【发布时间】:2013-07-16 06:31:32
【问题描述】:

我有 3 个文件,一个只有红色通道,一个只有绿色通道,一个只有蓝色通道。现在我想将这 3 张图像合并为一张,其中每张图像都是成品图像中的一个颜色通道。

我怎样才能用可可做到这一点?我有一个可行但速度太慢的解决方案:

NSBitmapImageRep *rRep = [[rImage representations] objectAtIndex: 0];
NSBitmapImageRep *gRep = [[gImage representations] objectAtIndex: 0];
NSBitmapImageRep *bRep = [[bImage representations] objectAtIndex: 0];
NSBitmapImageRep *finalRep = [rRep copy];
for (NSUInteger i = 0; i < [rRep pixelsWide]; i++) {
    for (NSUInteger j = 0; j < [rRep pixelsHigh]; j++) {
        CGFloat r = [[rRep colorAtX:i y:j] redComponent];
        CGFloat g = [[gRep colorAtX:i y:j] greenComponent];
        CGFloat b = [[bRep colorAtX:i y:j] blueComponent];
        [finalRep setColor:[NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0] atX:i y:j];
    }
}
NSData *data = [finalRep representationUsingType:NSJPEGFileType properties:[NSDictionary dictionaryWithObject:[NSNumber numberWithDouble:0.7] forKey:NSImageCompressionFactor]];
[data writeToURL:[panel URL] atomically:YES];

【问题讨论】:

    标签: image macos cocoa image-processing nsbitmapimagerep


    【解决方案1】:

    Accelerate.framework 提供了将 3 个平面图像组合到一个目标的功能: vImageConvert_Planar8toRGB888.
    我没有尝试过您的方法,但下面基于 vImage 的方法非常快。
    我能够在我的 Mac 上以约 0.1 秒的时间组合 1680x1050 图像的三个(R、G、B)平面。实际转换需要大约 1/3 的时间 - 其余的是设置和文件 IO。

    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
    {
        NSDate* start = [NSDate date];
    
        NSURL* redImageURL = [[NSBundle mainBundle] URLForImageResource:@"red"];
        NSURL* greenImageURL = [[NSBundle mainBundle] URLForImageResource:@"green"];
        NSURL* blueImageURL = [[NSBundle mainBundle] URLForImageResource:@"blue"];
        NSData* redImageData = [self newChannelDataFromImageAtURL:redImageURL];
        NSData* greenImageData = [self newChannelDataFromImageAtURL:greenImageURL];
        NSData* blueImageData = [self newChannelDataFromImageAtURL:blueImageURL];
    
        //We use our "Red" image to measure the dimensions. We assume that all images & the destination have the same size
        CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)redImageURL, NULL);
        NSDictionary* properties = (__bridge NSDictionary*)CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
        CGFloat width = [properties[(id)kCGImagePropertyPixelWidth] doubleValue];
        CGFloat height = [properties[(id)kCGImagePropertyPixelHeight] doubleValue];
    
        self.image = [self newImageWithSize:CGSizeMake(width, height) fromRedChannel:redImageData greenChannel:greenImageData blueChannel:blueImageData];
        NSLog(@"Combining 3 (R, G, B) planes of size %@ took:%fs", NSStringFromSize(CGSizeMake(width, height)), [[NSDate date] timeIntervalSinceDate:start]);
    }
    
    - (NSImage*)newImageWithSize:(CGSize)size fromRedChannel:(NSData*)redImageData greenChannel:(NSData*)greenImageData blueChannel:(NSData*)blueImageData
    {
        vImage_Buffer redBuffer;
        redBuffer.data = (void*)redImageData.bytes;
        redBuffer.width = size.width;
        redBuffer.height = size.height;
        redBuffer.rowBytes = [redImageData length]/size.height;
    
        vImage_Buffer greenBuffer;
        greenBuffer.data = (void*)greenImageData.bytes;
        greenBuffer.width = size.width;
        greenBuffer.height = size.height;
        greenBuffer.rowBytes = [greenImageData length]/size.height;
    
        vImage_Buffer blueBuffer;
        blueBuffer.data = (void*)blueImageData.bytes;
        blueBuffer.width = size.width;
        blueBuffer.height = size.height;
        blueBuffer.rowBytes = [blueImageData length]/size.height;
    
        size_t destinationImageBytesLength = size.width*size.height*3;
        const void* destinationImageBytes = valloc(destinationImageBytesLength);
        NSData* destinationImageData = [[NSData alloc] initWithBytes:destinationImageBytes length:destinationImageBytesLength];
        vImage_Buffer destinationBuffer;
        destinationBuffer.data = (void*)destinationImageData.bytes;
        destinationBuffer.width = size.width;
        destinationBuffer.height = size.height;
        destinationBuffer.rowBytes = [destinationImageData length]/size.height;
    
        vImage_Error result = vImageConvert_Planar8toRGB888(&redBuffer, &greenBuffer, &blueBuffer, &destinationBuffer, 0);
        NSImage* image = nil;
        if(result == kvImageNoError)
        {
            //TODO: If you need color matching, use an appropriate colorspace here
            CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
            CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)(destinationImageData));
            CGImageRef finalImageRef = CGImageCreate(size.width, size.height, 8, 24, destinationBuffer.rowBytes, colorSpace, kCGBitmapByteOrder32Big|kCGImageAlphaNone, dataProvider, NULL, NO, kCGRenderingIntentDefault);
            CGColorSpaceRelease(colorSpace);
            CGDataProviderRelease(dataProvider);
            image = [[NSImage alloc] initWithCGImage:finalImageRef size:NSMakeSize(size.width, size.height)];
            CGImageRelease(finalImageRef);
        }
        free((void*)destinationImageBytes);
        return image;
    }
    
    - (NSData*)newChannelDataFromImageAtURL:(NSURL*)imageURL
    {
        CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, NULL);
        if(imageSource == NULL){return NULL;}
        CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
        CFRelease(imageSource);
        if(image == NULL){return NULL;}
        CGColorSpaceRef colorSpace = CGImageGetColorSpace(image);
        CGFloat width = CGImageGetWidth(image);
        CGFloat height = CGImageGetHeight(image);
        size_t bytesPerRow = CGImageGetBytesPerRow(image);
        CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image);
        CGContextRef bitmapContext = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, bitmapInfo);
        NSData* data = NULL;
        if(NULL != bitmapContext)
        {
            CGContextDrawImage(bitmapContext, CGRectMake(0.0, 0.0, width, height), image);
            CGImageRef imageRef = CGBitmapContextCreateImage(bitmapContext);
            if(NULL != imageRef)
            {
                data = (NSData*)CFBridgingRelease(CGDataProviderCopyData(CGImageGetDataProvider(imageRef)));
            }
            CGImageRelease(imageRef);
            CGContextRelease(bitmapContext);
        }
        CGImageRelease(image);
        return data;
    }
    

    【讨论】:

    • 谢谢!正是我要找的:) 还有一个问题:一些图像中包含所有 3 个通道,但我只需要一个。我该怎么做?
    • 您的频道图片来自哪里?我用来测试上述方法的图像已经处于灰色空间中。有一些关于该主题的 SO 问题,例如(没试过):stackoverflow.com/questions/3340564/…
    • vImage_Buffer redBuffer2; redBuffer2.width = size.width; redBuffer2.height = size.height; redBuffer2.rowBytes = size.width; vImageConvert_RGB888toPlanar8(&redBuffer, &redBuffer2, nil, nil, kvImageNoFlags);我在“redBuffer.rowBytes = [redImageData length]/size.height;”下面添加了这个但它给了我一个 EXC_BAD_ACCESS 我做错了什么?
    【解决方案2】:

    您的程序创建了很多很多很多很多颜色对象。

    虽然您的程序可以简单地访问图像代表的bitmapData,但它需要您的程序了解很多关于位图表示的知识。

    在采用这种方法之前,您应该更愿意让 Quartz 完成繁重的工作,将每个图像渲染到 CGBitmapContext(例如使用 CGContextDrawImage(gtx, rect, img.CGImage)),然后从渲染结果中提取/复制渲染的组件值到目标 RGB 位图。

    如果您的输入不是多分量颜色模型(例如灰度),那么您应该渲染到源颜色模型以节省大量 CPU 时间和内存。

    【讨论】:

      猜你喜欢
      • 2012-02-21
      • 1970-01-01
      • 2019-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-26
      • 1970-01-01
      相关资源
      最近更新 更多