分析由NSAffineTransform和CILineOverlay过滤器生成的位图

时间:2010-06-12 16:16:16

标签: objective-c cocoa quartz-graphics

我正在尝试使用CIFilters链操作图像,然后检查生成的图像的每个字节(位图)。从长远来看,我不需要显示生成的图像(位图) - 我只需要在内存中“分析”它。但是近期我会在屏幕上显示它,以帮助调试。

我有一些“位图检查”代码在检查我用作输入(从JPG文件加载到NSImage中)的NSImage(位图表示)时按预期工作。当我在下面的代码生成的outputBitmap上使用它时,它有点按预期工作。更具体地说,当我使用NSAffineTransform过滤器创建outputBitmap时,outputBitmap包含我期望的数据。但是,如果我使用CILineOverlay过滤器来创建outputBitmap,则位图中的任何字节都不会包含任何数据。我相信这两个过滤器都按预期工作,因为当我在屏幕上显示结果时(通过outputImageView),它们看起来“正确”。然而,当我检查outputBitmaps时,从CILineOverlay过滤器创建的那个是“空”,而从NSAffineTransfer创建的那个包含数据。此外,如果我将两个过滤器链接在一起,最后得到的位图似乎只包含数据,如果我最后运行AffineTransform。对我来说似乎很奇怪???

我的理解(从阅读CI编程指南)是CIImage应该被视为“图像配方”而不是实际图像,因为在图像被“绘制”之前实际上不会创建图像。鉴于此,CIimage位图没有数据是有意义的 - 但我不明白为什么它在运行NSAffineTransform后有数据但在运行CILineOverlay转换后没有数据?基本上,我试图确定从CIImage(myResult)创建NSCIImageRep(下面的代码中的ir)是否相当于“绘制”CIImage - 换句话说,是否应该强制填充位图?如果有人知道答案,请告诉我 - 这将节省我几个小时的试验和错误实验!

最后,如果答案是“你必须绘制到图形上下文”......那么我还有另外一个问题:我是否需要按照Quartz 2D Programming Guide:Graphics Contexts中描述的内容做一些事情,清单2-7和2-8:绘制到位图图形上下文?这就是我要走的路径......但似乎很多代码只是为了强制将位图数据转储到我可以得到它的数组中。因此,如果有更简单或更好的方式,请告诉我。我只想在myResult中获取数据(应该是)并将其放入位图数组中,我可以在字节级访问它。而且因为我已经有了与NSBitmapImageRep一起使用的代码,除非出于某种原因这样做是个坏主意,这对我来说并不是很明显,所以我宁愿将myResult“转换”为NSBitmapImageRep。

CIImage * myResult = [transform valueForKey:@"outputImage"]; 
NSImage *outputImage;
NSCIImageRep *ir = [NSCIImageRep alloc];
ir = [NSCIImageRep imageRepWithCIImage:myResult];
outputImage = [[[NSImage alloc] initWithSize:
                              NSMakeSize(inputImage.size.width, inputImage.size.height)]
                             autorelease];
                    [outputImage addRepresentation:ir];
[outputImageView setImage: outputImage];
NSBitmapImageRep *outputBitmap = [[NSBitmapImageRep alloc] initWithCIImage: myResult];

谢谢, 亚当

编辑#1 - 对于Peter H.评论: 访问位图数据的示例代码......

for (row = 0; row < heightInPixels; row++)
  for (column = 0; column < widthInPixels; column++) {
    if (row == 1340) {  //just check this one row, that I know what to expect
        NSLog(@"Row 1340 column %d pixel redByte of pixel is %d",column,thisPixel->redByte);
    }
}

上面的结果(所有列包含相同的零/空值,这就是我所谓的“空”)...

2010-06-13 10:39:07.765 ImageTransform[5582:a0f] Row 1340 column 1664 pixel redByte of pixel is 0
2010-06-13 10:39:07.765 ImageTransform[5582:a0f] Row 1340 column 1665 pixel redByte of pixel is 0
2010-06-13 10:39:07.766 ImageTransform[5582:a0f] Row 1340 column 1666 pixel redByte of pixel is 0

如果我将%d更改为%h,则根本不打印(空白而不是“0”)。如果我将其更改为%@,则每行都会得到“(null)”,而不是上面显示的“0”。另一方面......当我只运行NSAffineTransform过滤器然后执行此代码时,打印的字节包含我期望的数据(无论我如何格式化NSLog输出,打印的东西)。

在6月14日添加更多代码......

// prior code retrieves JPG image from disk and loads into NSImage
CIImage * inputCIimage = [[CIImage alloc] initWithBitmapImageRep:inputBitmap];
if (inputCIimage == nil) {
  NSLog(@"Bailing out.  Could not create CI Image");
  return;
}
NSLog (@"CI Image created.  working on transforms...");

旋转图像的过滤器....这是以前在一个方法中,但我已经将它移动到“在线”,因为我一直试图弄清楚出了什么问题......

// rotate imageIn by degreesToRotate, using an AffineTransform      
CIFilter *transform = [CIFilter filterWithName:@"CIAffineTransform"];
[transform setDefaults];
[transform setValue:inputCIimage forKey:@"inputImage"];
NSAffineTransform *affineTransform = [NSAffineTransform transform];
[affineTransform transformPoint: NSMakePoint(inputImage.size.width/2, inputImage.size.height / 2)]; 
//inputImage.size.width /2.0,inputImage.size.height /2.0)];
[affineTransform rotateByDegrees:3.75];
[transform setValue:affineTransform forKey:@"inputTransform"];
CIImage * myResult2 = [transform valueForKey:@"outputImage"]; 

过滤以应用CILineOverlay过滤器...(之前也在方法中)

CIFilter *lineOverlay = [CIFilter filterWithName:@"CILineOverlay"];
[lineOverlay setDefaults];
[lineOverlay setValue: inputCIimage forKey:@"inputImage"];
// start off with default values, then tweak the ones needed to achieve desired results
[lineOverlay setValue: [NSNumber numberWithFloat: .07] forKey:@"inputNRNoiseLevel"]; //.07 (0-1)
[lineOverlay setValue: [NSNumber numberWithFloat: .71] forKey:@"inputNRSharpness"]; //.71 (0-2)
[lineOverlay setValue: [NSNumber numberWithFloat: 1] forKey:@"inputEdgeIntensity"]; //1 (0-200)
[lineOverlay setValue: [NSNumber numberWithFloat: .1] forKey:@"inputThreshold"]; //.1 (0-1)
[lineOverlay setValue: [NSNumber numberWithFloat: 50] forKey:@"inputContrast"]; //50 (.25-200)
CIImage *myResult2 = [lineOverlay valueForKey:@"outputImage"];  //apply the filter to the CIImage object and return it

最后......使用结果的代码......

if (myResult2 == Nil)
    NSLog(@"Transformations failed");
else {
NSLog(@"Finished transformations successfully ... now render final image");
// make an NSImage from the CIImage (to display it, during initial development)
NSImage *outputImage;
NSCIImageRep *ir = [NSCIImageRep alloc];
// show the tranformed output on screen...
ir = [NSCIImageRep imageRepWithCIImage:myResult2];
outputImage = [[[NSImage alloc] initWithSize:
              NSMakeSize(inputImage.size.width, inputImage.size.height)]
             autorelease];
[outputImage addRepresentation:ir];
[outputImageView setImage: outputImage];  //rotatedImage

此时,转换的图像在屏幕上显示得很好,无论我应用哪种转换以及我留下哪一个注释掉。如果我将变换“链接”在一起使得#1的输出进入#2,它甚至可以正常工作。所以,对我而言,这似乎表明过滤器正在运行。

然而......我真正需要使用的代码是“位图分析”代码,它正在检查Results2中(或“应该”)中的位图。并且该代码仅适用于CIAffineTransform过滤器产生的位图。当我用它来检查CILineOverlay产生的位图时,整个位图似乎只包含零。

所以这是用于分析的代码......

// this is the next line after the [outputImageView ...] shown above
[self findLeftEdge :myResult2];

然后这是findLeftEdge方法的代码...

- (void) findLeftEdge :(CIImage*)imageInCI {
    // find the left edge of the input image, assuming it will be the first non-white pixel
    // because we have already applied the Threshold filter

    NSBitmapImageRep *outputBitmap = [[NSBitmapImageRep alloc] initWithCIImage: imageInCI];
    if (outputBitmap == nil)
        NSLog(@"unable to create outputBitmap");
    else 
        NSLog(@"ouputBitmap image rep created -- samples per pixel = %d", [outputBitmap samplesPerPixel]);

    RGBAPixel 
        *thisPixel, 
        *bitmapPixels = (RGBAPixel *)[outputBitmap bitmapData];  

    int 
    row, 
    column,
    widthInPixels = [outputBitmap pixelsWide], 
    heightInPixels = [outputBitmap pixelsHigh];

    //RGBAPixel *leftEdge [heightInPixels];
    struct { 
        int pixelNumber;
        unsigned char pixelValue;
    } leftEdge[heightInPixels];

    // Is this necessary, or does objective-c always intialize it to zero, for me?
    for (row = 0; row < heightInPixels; row++) {
        leftEdge[row].pixelNumber = 0;
        leftEdge[row].pixelValue = 0;
    }

    for (row = 0; row < heightInPixels; row++)
        for (column = 0; column < widthInPixels; column++)  {
            thisPixel = (&bitmapPixels[((widthInPixels * row) + column)]);

            //red is as good as any channel, for this test (assume threshold filter already applied)
            //this should "save" the column number of the first non-white pixel encountered
            if (leftEdge[row].pixelValue < thisPixel->redByte) {
                leftEdge[row].pixelValue = thisPixel->redByte;  
                leftEdge[row].pixelNumber = column;
            }
            // For debugging, display contents of each pixel
            //NSLog(@"Row %d column %d pixel redByte of pixel is %@",row,column,thisPixel->redByte);
            // For debugging, display contents of each pixel on one row
            //if (row == 1340) {
            //  NSLog(@"Row 1340 column %d pixel redByte of pixel is %@",column,thisPixel->redByte);
            //}

        }

    // For debugging, display the left edge that we discovered
    for (row = 0; row < heightInPixels; row++) {
        NSLog(@"Left edge on row %d was at pixel #%d", row, leftEdge[row].pixelNumber);
    }

    [outputBitmap release];  
}

这是另一个过滤器。当我使用它时,我确实在“输出位图”中获取数据(就像旋转滤波器一样)。所以只有AffineTransform才能在结果位图中为我提供数据......

- (CIImage*) applyCropToCI:(CIImage*) imageIn {
rectToCrop { 
    // crop the rectangle specified from the input image
    CIFilter *crop = [CIFilter filterWithName:@"CICrop"];
    [crop setDefaults];  
    [crop setValue:imageIn forKey:@"inputImage"];
//  [crop setValue:rectToCrop forKey:@"inputRectangle"];  //vector defaults to 0,0,300,300
    //CIImage * myResult = [transform valueForKey:@"outputImage"]; //this is the way it was "in-line", before putting this code into a method
    return [crop valueForKey:@"outputImage"];   //does this need to be retained?
}

2 个答案:

答案 0 :(得分:0)

for (row = 0; row < heightInPixels; row++)
  for (column = 0; column < widthInPixels; column++) {
    if (row == 1340) {  //just check this one row, that I know what to expect
        NSLog(@"Row 1340 column %d pixel redByte of pixel is %d",column,thisPixel->redByte);
  }
}

除了语法错误之外,此代码不起作用,因为您从未分配给thisPixel。您没有循环索引索引,因为您实际上从未在这些索引处查找像素值并将其分配给thisPixel以进行检查。

NSLog声明之前添加此类作业。

此外,如果您关心的唯一行是1340,则无需循环遍历行。使用if语句检查1340是否小于高度,如果是,则只执行列循环。 (另外,不要在你的代码中嵌入这样的魔术数字文字。给那个常数一个名称来解释数字1340的重要性 - 即为什么它是你唯一关心的行。)< / p>

答案 1 :(得分:0)

您声称位图数据包含“全零”,但您只查看每个像素一个字节。你假设第一个组件是红色组件,而你假设数据是每个组件一个字节;如果数据是alpha-first或浮点数,则这些假设中的一个或两个都是错误的。

使用您分配的缓冲区以您想要的任何格式创建位图上下文,并将图像渲染到该上下文中。然后,您的缓冲区将以您期望的格式包含图像。

您可能还希望从基于结构的访问切换到基于字节的访问 - 即pixels[(row*bytesPerRow)+col],将col递增每个像素的组件数。当您使用结构访问组件时,字节顺序很容易变得令人头疼。