I am looking for a way to get a histogram of an image on the iPhone. The OpenCV library is way too big to be included in my app (OpenCV is about 70MB compiled), but I can use OpenGL. However, I have no idea on how to do either of these.
I have found how to get the pixels of the image, but cannot form a histogram. This seems like it should be simple, but I don't know how to store uint8_t into an array.
Here is the relevant question/answer for finding pixels:
Getting RGB pixel data from CGImage
The uint8_t* is just a pointer to a c array containing the bytes of the given color, i.e. {r, g, b, a} or whatever the color byte layout is for your image buffer.
So, referencing the link you provided, and the definition of histogram:
//Say we're in the inner loop and we have a given pixel in rgba format
const uint8_t* pixel = &bytes[row * bpr + col * bytes_per_pixel];
//Now save to histogram_counts uint32_t[4] planes r,g,b,a
//or you could just do one for brightness
//If you want to do data besides rgba, use bytes_per_pixel instead of 4
for (int i=0; i<4; i++) {
//Increment count of pixels with this value
histogram_counts[i][pixel[i]]++;
}
You can take RGB color of your image with CGRef. Look at the below method which I used for this.
- (UIImage *)processUsingPixels:(UIImage*)inputImage {
// 1. Get the raw pixels of the image
UInt32 * inputPixels;
CGImageRef inputCGImage = [inputImage CGImage];
NSUInteger inputWidth = CGImageGetWidth(inputCGImage);
NSUInteger inputHeight = CGImageGetHeight(inputCGImage);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4;
NSUInteger bitsPerComponent = 8;
NSUInteger inputBytesPerRow = bytesPerPixel * inputWidth;
inputPixels = (UInt32 *)calloc(inputHeight * inputWidth, sizeof(UInt32));
CGContextRef context = CGBitmapContextCreate(inputPixels, inputWidth, inputHeight,
bitsPerComponent, inputBytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
// 3. Convert the image to Black & White
for (NSUInteger j = 0; j < inputHeight; j++) {
for (NSUInteger i = 0; i < inputWidth; i++) {
UInt32 * currentPixel = inputPixels + (j * inputWidth) + i;
UInt32 color = *currentPixel;
// Average of RGB = greyscale
UInt32 averageColor = (R(color) + G(color) + B(color)) / 3.0;
*currentPixel = RGBAMake(averageColor, averageColor, averageColor, A(color));
}
}
// 4. Create a new UIImage
CGImageRef newCGImage = CGBitmapContextCreateImage(context);
UIImage * processedImage = [UIImage imageWithCGImage:newCGImage];
// 5. Cleanup!
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
return processedImage;
}
Related
I have the source code for a video decoder application written in C, which I'm now porting on iphone.
My problem is as follows:
I have RGBA pixel data for a frame in a buffer that I need to display on the screen. My buffer is of type unsigned char. (I cannot change it to any other data type as the source code is too huge and not written by me.)
Most of the links I found on the net say about how to "draw and display pixels" on the screen or how to "display pixels present in an array", but none of then say how to "display pixel data present in a buffer".
I'm planning to use quartz 2D. All I need to do is just display the buffer contents on the screen. No modifications! Although my problem sounds very simple, there isn't any API that I could find to do the same. I couldn't find any appropriate link or document that was useful enough.
Kindly help!
Thanks in advance.
You can use the CGContext data structure to create a CGImage from raw pixel data. I've quickly written a basic example:
- (CGImageRef)drawBufferWidth:(size_t)width height:(size_t)height pixels:(void *)pixels
{
unsigned char (*buf)[width][4] = pixels;
static CGColorSpaceRef csp = NULL;
if (!csp) {
csp = CGColorSpaceCreateDeviceRGB();
}
CGContextRef ctx = CGBitmapContextCreate(
buf,
width,
height,
8, // 8 bits per pixel component
width * 4, // 4 bytes per row
csp,
kCGImageAlphaPremultipliedLast
);
CGImageRef img = CGBitmapContextCreateImage(ctx);
CGContextRelease(ctx);
return img;
}
You can call this method like this (I've used a view controller):
- (void)viewDidLoad
{
[super viewDidLoad];
const size_t width = 320;
const size_t height = 460;
unsigned char (*buf)[width][4] = malloc(sizeof(*buf) * height);
// fill up `buf` here
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
buf[y][x][0] = x * 255 / width;
buf[y][x][1] = y * 255 / height;
buf[y][x][2] = 0;
buf[y][x][3] = 255;
}
}
CGImageRef img = [self drawBufferWidth:320 height:460 pixels:buf];
self.imageView.image = [UIImage imageWithCGImage:img];
CGImageRelease(img);
}
In the following links it gives the result as shown below image
https://github.com/BloodAxe/opencv-ios-template-project/downloads
http://aptogo.co.uk/2011/09/opencv-framework-for-ios/
i changed the code to
COLOR_RGB2GRAY to COLOR_BGR2BGRA it give me a error says "OpenCV Error: Unsupported format or combination of formats () in cvCanny"
(or)
CGColorSpaceCreateDeviceGray to CGColorSpaceCreateDeviceRGB
I am Totally confusing where to change the code...
I need the output as "white color with black lines" instead of "black color with gray lines
Please Guide me
Thanks a lot in advance
In OpenCVClientViewController.mm include this method (copied from https://stackoverflow.com/a/6672628/) then the image will be converted as shown below:
-(void)inverColors
{
NSLog(#"inverColors called ");
// get width and height as integers, since we'll be using them as
// array subscripts, etc, and this'll save a whole lot of casting
CGSize size = self.imageView.image.size;
int width = size.width;
int height = size.height;
// Create a suitable RGB+alpha bitmap context in BGRA colour space
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *memoryPool = (unsigned char *)calloc(width*height*4, 1);
CGContextRef context = CGBitmapContextCreate(memoryPool, width, height, 8, width * 4, colourSpace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colourSpace);
// draw the current image to the newly created context
CGContextDrawImage(context, CGRectMake(0, 0, width, height), [self.imageView.image CGImage]);
// run through every pixel, a scan line at a time...
for(int y = 0; y < height; y++)
{
// get a pointer to the start of this scan line
unsigned char *linePointer = &memoryPool[y * width * 4];
// step through the pixels one by one...
for(int x = 0; x < width; x++)
{
// get RGB values. We're dealing with premultiplied alpha
// here, so we need to divide by the alpha channel (if it
// isn't zero, of course) to get uninflected RGB. We
// multiply by 255 to keep precision while still using
// integers
int r, g, b;
if(linePointer[3])
{
r = linePointer[0] * 255 / linePointer[3];
g = linePointer[1] * 255 / linePointer[3];
b = linePointer[2] * 255 / linePointer[3];
}
else
r = g = b = 0;
// perform the colour inversion
r = 255 - r;
g = 255 - g;
b = 255 - b;
// multiply by alpha again, divide by 255 to undo the
// scaling before, store the new values and advance
// the pointer we're reading pixel data from
linePointer[0] = r * linePointer[3] / 255;
linePointer[1] = g * linePointer[3] / 255;
linePointer[2] = b * linePointer[3] / 255;
linePointer += 4;
}
}
// get a CG image from the context, wrap that into a
// UIImage
CGImageRef cgImage = CGBitmapContextCreateImage(context);
UIImage *returnImage = [UIImage imageWithCGImage:cgImage];
// clean up
CGImageRelease(cgImage);
CGContextRelease(context);
free(memoryPool);
// and return
self.imageView.image= returnImage;
}
// Called when the user changes either of the threshold sliders
- (IBAction)sliderChanged:(id)sender
{
self.highLabel.text = [NSString stringWithFormat:#"%.0f", self.highSlider.value];
self.lowLabel.text = [NSString stringWithFormat:#"%.0f", self.lowSlider.value];
[self processFrame];
}
I have firstly convert the image to raw pixels and again convert the pixels back to UIImage, after converting the image it changes it color and also become some transparent, I have tried a lot but not able to get the problem. Here is my code:
-(UIImage*)markPixels:(NSMutableArray*)pixels OnImage:(UIImage*)image{
CGImageRef inImage = image.CGImage;
// Create off screen bitmap context to draw the image into. Format ARGB is 4 bytes for each pixel: Alpa, Red, Green, Blue
CGContextRef cgctx = [self createARGBBitmapContextFromImage:inImage];
if (cgctx == NULL) { return nil; /* error */ }
size_t w = CGImageGetWidth(inImage);
size_t h = CGImageGetHeight(inImage);
CGRect rect = {{0,0},{w,h}};
// Draw the image to the bitmap context. Once we draw, the memory
// allocated for the context for rendering will then contain the
// raw image data in the specified color space.
CGContextDrawImage(cgctx, rect, inImage);
// Now we can get a pointer to the image data associated with the bitmap
// context.
int r = 3;
int p = 2*r+1;
unsigned char* data = CGBitmapContextGetData (cgctx);
int i = 0;
while (data[i]&&data[i+1]) {
// NSLog(#"%d",pixels[i]);
i++;
}
NSLog(#"%d %zd %zd",i,w,h);
NSLog(#"%ld",sizeof(CGBitmapContextGetData (cgctx)));
for(int i = 0; i< pixels.count-1 ; i++){
NSValue*touch1 = [pixels objectAtIndex:i];
NSValue*touch2 = [pixels objectAtIndex:i+1];
NSArray *linePoints = [self returnLinePointsBetweenPointA:[touch1 CGPointValue] pointB:[touch2 CGPointValue]];
for(NSValue *touch in linePoints){
NSLog(#"point = %#",NSStringFromCGPoint([touch CGPointValue]));
CGPoint location = [touch CGPointValue];
for(int i = -r ; i<p ;i++)
for(int j= -r; j<p;j++)
{
if(i<=0 && j<=0 && i>image.size.height && j>image.size.width)
continue;
NSInteger index = (location.y+i) * w*4 + (location.x+j)* 4;
index = 0;
data[index +3] = 125;
}
}
}
// When finished, release the context
CGContextRelease(cgctx);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGDataProviderRef dp = CGDataProviderCreateWithData(NULL, data, w*h*4, NULL);
CGImageRef img = CGImageCreate(w, h, 8, 32, 4*w, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big, dp, NULL, NO, kCGRenderingIntentDefault);
UIImage* ret_image = [UIImage imageWithCGImage:img];
CGImageRelease(img);
CGColorSpaceRelease(colorSpace);
// Free image data memory for the context
if (data) { free(data); }
return ret_image;
}
First one is original image and second image is after applying this code.
You have to ask the CGImageRef if it uses alpha or not, and the format of the components per pixel - look at all the CGImageGet... functions. Most likely the image is not ARGB but BGRA.
I often create and render pure green images then print out the first pixel to insure I got it right (BGRA -> 0 255 0 255) etc. It really gets confusing with host order etc and alpha first or last (does that mean before host order is applied before or after?)
EDIT: You told the CGDataProviderCreateWithData to use 'kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big', but I don't see you asking the original image for how its configured. My guess is that changing 'kCGBitmapByteOrder32Big' to 'kCGBitmapByteOrder32Little' will fix your problem but the alpha may be wrong too.
Images can have different values for alpha and byte order so you really need to ask the original image how its configured then adapt to that (or remap the bytes in memory to whatever format you want.)
I'm currently attempting to port a Java program over to iOs which utilzes BufferedImage's .setRGB method to individually manipulate images' pixels like so
image.setRGB(x, y, color);
In Objective-C I'm using UIImage in place of BufferedImage and what I'd like to know is if there is an equivalent way of doing Java's setRGB , or must I convert the image to a byte array, manipulate the pixels and then use ImageWithData?
You have to convert the image into a bitmap, use setRGB on the bitmap, then create an image with the bitmap and draw it or assign the new image to your image views contents.
Why? The pixels are likely in an opaque format on the other side of the GPU where an app's ARM code can't access them.
You can use following Method to access pixels and manipulating them
-(UIImage*) applyFilterContext:(void*)context
{
CGImageRef inImage = self.CGImage;
CFDataRef m_DataRef = CGDataProviderCopyData(CGImageGetDataProvider(inImage));
UInt8 * m_PixelBuf = (UInt8 *) CFDataGetBytePtr(m_DataRef);
int length = CFDataGetLength(m_DataRef);
for (int i=0; i<length; i+=4)
{
filterGreyScale(m_PixelBuf, i, context);
}
//Create Context
CGContextRef ctx = CGBitmapContextCreate(m_PixelBuf,
CGImageGetWidth(inImage),
CGImageGetHeight(inImage),
CGImageGetBitsPerComponent(inImage),
CGImageGetBytesPerRow(inImage),
CGImageGetColorSpace(inImage),
CGImageGetBitmapInfo(inImage)
);
CGImageRef imageRef = CGBitmapContextCreateImage(ctx);
CGContextRelease(ctx);
UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CFRelease(m_DataRef);
return finalImage;
}
//manipulating pixels to to Greyscale values
void filterGreyScale (UInt8 *pixelBuf, UInt32 offset, void *context)
{
int r = offset;
int g = offset+1;
int b = offset+2;
int red = pixelBuf[r];
int green = pixelBuf[g];
int blue = pixelBuf[b];
uint32_t gray = 0.3 * red + 0.59 * green + 0.11 * blue;
pixelBuf[r] = gray;
pixelBuf[g] = gray;
pixelBuf[b] = gray;
}
Here is an example of how to get the pixel data from an image:
http://developer.apple.com/library/mac/#qa/qa1509/_index.html
Then you could change the color of the data this way:
// Set the color of the pixel to 50% grey + 50% alpha
data[offset+0] = 128;
data[offset+1] = 128;
data[offset+2] = 128;
data[offset+3] = 128;
Here is the code I ended up using to set a pixel
-(void)setPixel:(UInt8*)buffer width:(int)width x:(int)x y:(int)y r:(int)r g:(int)g b:(int)b
{
buffer[x*4 + y*(width*4)] = r;
buffer[x*4 + y*(width*4)+1] = g;
buffer[x*4 + y*(width*4)+2] = b;
}
I'm trying to do something very simple:
1) Draw a UIImage into a CG bitmap context
2) Get a pointer to the data of the image
3) iterate over all pixels and just set all R G B components to 0 and alpha to 255. The result should appear pure black.
This is the original image I am using. 200 x 40 pixels, PNG-24 ARGB premultiplied alpha (All alpha values == 255):
This is the result (screenshot from Simulator), when I do not modify the pixels. Looks good:
This is the result, when I do the modifications! It looks like if the modification was incomplete. But the for-loops went over EVERY single pixel. The counter proves it: Console reports modifiedPixels = 8000 which is exactly 200 x 40 pixels. It looks always exactly the same.
Note: The PNG image I use has no alpha < 255. So no transparent pixels.
This is how I create the context. Nothing special...
int bitmapBytesPerRow = (width * 4);
int bitmapByteCount = (bitmapBytesPerRow * imageHeight);
colorSpace = CGColorSpaceCreateDeviceRGB();
bitmapData = malloc(bitmapByteCount);
bitmapContext = CGBitmapContextCreate(bitmapData,
width,
height,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
CGImageAlphaPremultipliedFirst);
Next, I draw the image into that bitmapContext, and obtain the data like this:
void *data = CGBitmapContextGetData(bitmapContext);
This is the code which iterates over the pixels to modify them:
size_t bytesPerRow = CGImageGetBytesPerRow(img);
NSInteger modifiedPixels = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
long int offset = bytesPerRow * y + 4 * x;
// ARGB
unsigned char alpha = data[offset];
unsigned char red = data[offset+1];
unsigned char green = data[offset+2];
unsigned char blue = data[offset+3];
data[offset] = 255;
data[offset+1] = 0;
data[offset+2] = 0;
data[offset+3] = 0;
modifiedPixels++;
}
}
When done, I obtain a new UIImage from the bitmap context and display it in a UIImageView, to see the result:
CGImageRef imageRef = CGBitmapContextCreateImage(bitmapContext);
UIImage *img = [[UIImage alloc] initWithCGImage:imageRef];
Question:
What am I doing wrong?
Is this happening because I modify the data while iterating over it? Must I duplicate it?
Use CGBitmapContextGetBytesPerRow(bitmapContext) to get bytesPerRow instead getting from image (image has only 3 bytes per pixels if it hasn't alpha informations)
Might you're getting wrong height or width.... and by the way 240x40=9600 not 8000 so that's for sure that you're not iterating over each and every pixel.