Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get meaningful CIAreaHistogram output?

I want to calculate the histogram of a CGImage. I am using the CIAreaHistogram built-in CoreImage filter.

Justin Mrkva has done something along similar lines. He says:

I get the CIImage for the histogram itself, which I then run through a custom kernel (see end of post) to set alpha values to 1 (since otherwise the alpha value from the histogram calculations is premultiplied) and then convert it to an NSBitmapImageRep.

My question is: is it possible to get the histogram data without having to create a custom kernel? If so, how?

The following code simply tries to render the histogram without chaning the alpha values:

- (void)printHistogram:(CGImageRef)img {

    NSNumber* buckets = @10;

    CIImage* img_ = [CIImage imageWithCGImage:img];

    CIFilter* histF = [CIFilter filterWithName:@"CIAreaHistogram"];
    [histF setValue:img_ forKey:@"inputImage"];
    [histF setValue:[CIVector vectorWithX:0.0
                                        Y:0.0
                                        Z:CGImageGetWidth(img)
                                        W:CGImageGetHeight(img)]
             forKey:@"inputExtent"];
    [histF setValue:buckets forKey:@"inputCount"];
    [histF setValue:@1.0 forKey:@"inputScale"];

    CIImage* histImg = [histF valueForKey:@"outputImage"];

    int rowBytes = [buckets intValue] * 4; // ARGB has 4 components
    uint8_t byteBuffer[rowBytes]; // Buffer to render into
    CGColorSpaceRef cspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);

    CIContext* ctx = [[CIContext alloc] init];
    [ctx render:histImg
       toBitmap:byteBuffer
       rowBytes:rowBytes
         bounds:[histImg extent]
         format:kCIFormatARGB8
     colorSpace:cspace];

    CGColorSpaceRelease(cspace);

    for (int i=0; i<[buckets intValue]; i++) {
        const uint8_t* pixel = &byteBuffer[i*4];
        printf("%d:%u-%u-%u-%u\n",i,pixel[0],pixel[1],pixel[2],pixel[3]);
    }

}   

Giving the output (when run on a color photo):

0:0-0-0-0
1:0-0-0-0
2:0-0-0-0
3:0-0-0-0
4:0-0-0-0
5:0-0-0-0
6:0-0-0-0
7:0-0-0-0
8:0-0-0-0
9:255-33-6-7

I tried using CIColorMatrix to set the alpha values to 1.0 before rendering:

CIFilter* biasF = [CIFilter filterWithName:@"CIColorMatrix"];
[biasF setDefaults];
[biasF setValue:histImg forKey:@"inputImage"];
[biasF setValue:[CIVector vectorWithX:0.0 Y:0.0 Z:0.0 W:1.0] forKey:@"inputBiasVector"];

Even though the output format is ARGB, from what I understand from the Core Image Filter Reference, the alpha component is the last value in the vector (thus W:1.0).

But this yielded the following output:

0:255-255-255-255
1:255-255-255-255
2:255-255-255-255
3:255-255-255-255
4:255-255-255-255
5:255-255-255-255
6:255-255-255-255
7:255-255-255-255
8:255-255-0-255
9:255-66-11-15

All help and advice will be much appreciated!


EDIT: I know this question seems similar. However, the accepted answer stipulates:

The short of it is: you need to read the values as floats, not ints, which means you'll have to hook up a CGBitmapContext to blit to. Or if you keep everything in CI land, you'll need another filter to read the data and print something out with it.

However, looking at Justin Mrkva's question makes me think that getting integer values should be possible... Please let me know if there is an error in my thinking.

Thanks again!


EDIT 2: Fist of all, thank you to both David and jstn for their comments. Sorry it took so long for me to come back to this. I was working around the clock on a project (in fact it was that project that led me to this problem, but I ended up using an altogether different approach that no longer utilizes CIAreaHistogram). Now that I finally have some time on my hands, I wanted to get back to this. Even though I don't need it per se, I still want to understand how this thing really works!

Following David Hayward's suggestions, I made the following modifications.

- (void)printHistogram:(CGImageRef)img {

    NSNumber* buckets = @10;

    CIImage* img_ = [CIImage imageWithCGImage:img];

    CIFilter* histF = [CIFilter filterWithName:@"CIAreaHistogram"];
    [histF setValue:img_ forKey:@"inputImage"];
    [histF setValue:[CIVector vectorWithX:0.0
                                        Y:0.0
                                        Z:CGImageGetWidth(img)
                                        W:CGImageGetHeight(img)]
             forKey:@"inputExtent"];
    [histF setValue:buckets forKey:@"inputCount"];
    [histF setValue:@1.0 forKey:@"inputScale"];

    CIImage* histImg = [histF valueForKey:@"outputImage"];

    NSUInteger arraySize = [buckets intValue] * 4; // ARGB has 4 components

    // CHANGE 1: Since I will be rendering in float values, I set up the
    //           buffer using CGFloat
    CGFloat byteBuffer[arraySize]; // Buffer to render into

    // CHANGE 2: I wasn't supposed to call [[CIContext alloc] init]
    //           this is a more proper way of getting the context
    CIContext* ctx = [[NSGraphicsContext currentContext] CIContext];

    // CHANGE 3: I use colorSpace:NULL to use the output cspace of the ctx
    // CHANGE 4: Format is now kCIFormatRGBAf
    [ctx render:histImg
       toBitmap:byteBuffer
       rowBytes:arraySize
         bounds:[histImg extent]
         format:kCIFormatRGBAf
     colorSpace:NULL]; // uses the output cspace of the contetxt

    // CHANGE 5: I print the float values
    for (int i=0; i<[buckets intValue]; i++) {
        const CGFloat* pixel = &byteBuffer[i*4];
        printf("%d: %0.2f , %0.2f , %0.2f , %0.2f\n", i,pixel[0],pixel[1],pixel[2],pixel[3]);
    }
}   

This gives the following output:

0: 0.00 , 0.00 , 0.00 , 0.00
1: 0.00 , 0.00 , 0.00 , 0.00
2: 0.00 , 0.00 , 0.00 , 0.00
3: 0.00 , 0.00 , 0.00 , 0.00
4: 0.00 , 0.00 , 0.00 , 0.00
5: 0.00 , 0.00 , 0.00 , 0.00
6: 0.00 , 0.00 , 1.00 , 0.00
7: 0.00 , 0.00 , 0.00 , 0.00
8: 0.00 , 0.00 , 0.00 , 0.00
9: 3.00 , 0.00 , 0.00 , 0.00

Playing around with variations of formats and how the information is parsed yields wildly different and absurd outputs.

I'm quite sure the trouble lies in not properly understanding precisely how the bitmap data is represented.

Any further suggestions?

like image 836
Oral Okan Avatar asked Nov 10 '22 22:11

Oral Okan


1 Answers

Three suggestions:

  • use inputScale to gain the histogram counts up. If inputScale is 1, then a resulting histogram bin value will be 1.0 (255 if rendered to ARGB8) if the entire area has that bin value)
  • pass CI's working colorspace to render:toBitmap:. On Mavericks the working space is kCGColorSpaceGenericRGBLinear. On Yosemite it is linear sRGB.
  • on OS X you can use kCIFormatRGBAf to get float data
  • on iOS you can use kCIFormatRGBAh to get half-float data
like image 115
David Hayward Avatar answered Nov 26 '22 15:11

David Hayward