How to check if an image is B&W in iOS - ios

I have an UIImage that shows a photo downloaded from the net.
I would like to know away to programmatically discover if the image is in B&W or Color.

If you dont mind a computing intensive task and you want the job done, check pixel per pixel the image.
The idea is to check if all R G B channels for each single pixels are similar, for example a pixel with RGB 45-45-45 is a gray, and also 43-42-44 because all channels are close to each other. I'm looking that every channel has a similar value (i am using a threshold of 10 but it's just random, you have to do some tests)
As soon you have enought pixels that are above your threshold you can break the loop an flag the image as colored
the code is not tested, is just an idea, and hopefully without leaks.
// load image
CGImageRef imageRef = yourUIImage.CGImage
CFDataRef cfData = CGDataProviderCopyData(CGImageGetDataProvider(imageRef));
NSData * data = (NSData *) cfData;
char *pixels = (char *)[data bytes];
const int threshold = 10; //define a gray threshold
for(int i = 0; i < [data length]; i += 4)
{
Byte red = pixels[i];
Byte green = pixels[i+1];
Byte blue = pixels[i+2];
//check if a single channel is too far from the average value.
//greys have RGB values very close to each other
int average = (red+green+blue)/3;
if( abs(average - red) >= threshold ||
abs(average - green) >= threshold ||
abs(average - blue) >= threshold )
{
//possibly its a colored pixel.. !!
}
}
CFRelease(cfData);

Related

how to remove a stamp from an image with opencv

I am working on a OCR project, and in the preprocessing, some RED stamps need to be removed, so that the text near the stamps could be detected. I try a lot of methods(like change the values of pixel, threshold in Red channel) but fail.
Any suggestions are highly appreciated.
Python, C++, Java or what? Since you didn't state the OpenCV implementation you are using, I'm giving my answer in C++.
An option is to use the HSV color space to filter out the range of red values that defines the seal. My approach is to use the CMYK color space to filter everything except the black (or dark) text. It should do a pretty good job on printed media, which is your case.
//read input image:
std::string imageName = "C://opencvImages//seal.png";
cv::Mat imageInput = cv::imread( imageName );
Now, perform the CMYK conversion. OpenCV does not support this operation out of the box, bear with me as I provide the helper function at the end of this post.
//CMYK conversion:
std::vector<cv::Mat> cmyk;
cmyk = rgb2cmyk( imageInput );
//This is the Black channel:
cv::Mat blackChannel = cmyk[3].clone();
This is the image of the black channel; it is nice how everything that is not black (or dark) practically disappears!
Now, optionally, enhance the result applying brightness and contrast adjustment. Just try to separate the text from the background a little bit better; we want some defined pixel distributions to get a nice binary image.
//Brightness and contrast adjustment:
float alpha = 2.0;
float beta = -50.0;
contrastBrightnessAdjustment( blackChannel, alpha, beta );
Again, OpenCV does not offer brightness and contrast adjustment out of the box; however, its implementation is very easy. Hold on a little bit, and let me show you the result of this operation:
Nice. Let's Otsu-threshold this bad boy to get a nice binary image containing the clean text:
cv::threshold( blackChannel, binaryImage ,0, 255, cv::THRESH_OTSU );
This is what you get:
Now, the RGB to CMYK conversion function. I'm using the following implementation. The function receives an RGB image and returns a vector containing each of the CMYK channels
std::vector<cv::Mat> rgb2cmyk( cv::Mat& inputImage ){
std::vector<cv::Mat> cmyk;
for (int i = 0; i < 4; i++) {
cmyk.push_back( cv::Mat( inputImage.size(), CV_8UC1 ) );
}
std::vector<cv::Mat> inputRGB;
cv::split( inputImage, inputRGB );
for (int i = 0; i < inputImage.rows; i++)
{
for (int j = 0; j < inputImage.cols; j++)
{
float r = (int)inputRGB[2].at<uchar>(i, j) / 255.;
float g = (int)inputRGB[1].at<uchar>(i, j) / 255.;
float b = (int)inputRGB[0].at<uchar>(i, j) / 255.;
float k = std::min(std::min(1-r, 1-g), 1-b);
cmyk[0].at<uchar>(i, j) = (1 - r - k) / (1 - k) * 255.;
cmyk[1].at<uchar>(i, j) = (1 - g - k) / (1 - k) * 255.;
cmyk[2].at<uchar>(i, j) = (1 - b - k) / (1 - k) * 255.;
cmyk[3].at<uchar>(i, j) = k * 255.;
}
}
return cmyk;
}
And the contrastBrightnessAdjustment function is this, implemented using pointer arithmetic. The function receives a grayscale image and applies the linear transformation via the alpha and beta parameters:
void contrastBrightnessAdjustment( cv::Mat inputImage, float alpha, int beta ){
cv::MatIterator_<cv::Vec3b> it, end;
for (it = inputImage.begin<cv::Vec3b>(), end = inputImage.end<cv::Vec3b>(); it != end; ++it) {
uchar &pixel = (*it)[0];
pixel = cv::saturate_cast<uchar>(alpha*pixel+beta);
}
}

How to know if a photo is black or too dark?

I have a UIImagePickerViewController where the user takes a photo. My problem is how to know before uploading the photo to the server if the user is sending a dark photo. I mean a totally or nearly black.
I was researching and I found this:
const UInt8 *pixels = CFDataGetBytePtr(imageData);
UInt8 blackThreshold = 10; // or some value close to 0
int bytesPerPixel = 4;
for(int x = 0; x < width1; x++) {
for(int y = 0; y < height1; y++) {
int pixelStartIndex = (x + (y * width1)) * bytesPerPixel;
UInt8 alphaVal = pixels[pixelStartIndex]; // can probably ignore this value
UInt8 redVal = pixels[pixelStartIndex + 1];
UInt8 greenVal = pixels[pixelStartIndex + 2];
UInt8 blueVal = pixels[pixelStartIndex + 3];
if(redVal < blackThreshold && blueVal < blackThreshold && greenVal < blackThreshold) {
//This pixel is close to black...do something with it
}
}
}
However, I don't know how to apply the algorithm.
Yep that's a fairly simple way of doing it. You could, for example, iterate through and see what percentage of the pixels are pure black (i.e. clipped shadows) or nearly black. Or you could average the pixel colors throughout the whole image and see if it falls below a certain threshold. There are lots of approaches and these two might be a tad simplistic, but I'm not sure if this calls for anything particularly sophisticated. What threshold you want to use is up to you.
Also, while it has little practical impact, if I was going to be picky about the algorithm, I might only perform the "brightness" logic if the alphaVal was over a certain threshold, as well, as the color information is meaningless at transparent portions of image. Having said that, real photos rarely have any transparency, so this may be non-issue.
FYI, here is Apple's code for retrieving the pixel buffer. It's an oldie, but a goodie. (If I recall correctly, the only hassle is that the kCGImageAlphaPremultipliedFirst reference in CreateARGBBitmapContext must be cast with (CGBitmapInfo).)
By the way, if you're trying to determine the luminance of a particular pixel, one common algorithm is:
luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue

Subtract blue background from image by OpenCV C++

I am a beginner in OpenCV and C++, but now I have to find a solution for this problem:
I have an image of a person with blue background, now I have to subtract background from image then replace it by another image.
Now I think there are 2 ways to resolve this problem, but I don't know which is better:
Solution 1:
Convert image to B&W
Use it as a mask to subtract background.
Solution 2:
Using coutour to find the background,
and then subtract it.
I have already implemented as solution 1, but the result is not as my expect.
Do you know there's another better solution or somebody already implement it as source code?
I will appreciate your help.
I update my source code here, please give me some comment
//Get the image with person
cv::Mat imgRBG = imread("test.jpg");
//Convert this image to grayscale
cv::Mat imgGray = imread("test.jpg",CV_LOAD_IMAGE_GRAYSCALE);
//Get the background from image
cv::Mat background = imread("paris.jpg");
cv::Mat imgB, imgW;
//Image with black background but inside have some area black
threshold(imgGray, imgB, 200, 255, CV_THRESH_BINARY_INV);
cv::Mat imgTemp;
cv::Mat maskB, maskW;
cv::Mat imgDisplayB, imgDisplayW;
cv::Mat imgDisplay1, imgDisplay2, imgResult;
//Copy image with black background, overide the original image
//Now imgTemp has black background wrap the human image, and inside the person, if there're some white area, they will be replace by black area
imgRBG.copyTo(imgTemp, imgB);
//Now replace the black background with white color
cv::floodFill(imgTemp, cv::Point(imgTemp.cols -10 ,10), cv::Scalar(255.0, 255.0, 255.0));
cv::floodFill(imgTemp, cv::Point(10,10), cv::Scalar(255.0, 255.0, 255.0));
cv::floodFill(imgTemp, cv::Point(10,imgTemp.rows -10), cv::Scalar(255.0, 255.0, 255.0));
cv::floodFill(imgTemp, cv::Point(imgTemp.cols -10,imgTemp.rows -10), cv::Scalar(255.0, 255.0, 255.0));
//Convert to grayscale
cvtColor(imgTemp,imgGray,CV_RGB2GRAY);
//Convert to B&W image, now background is black, other is white
threshold(imgGray, maskB, 200, 255, CV_THRESH_BINARY_INV);
//Convert to B&W image, now background is white, other is black
threshold(imgGray, maskW, 200, 255, CV_THRESH_BINARY);
//Replace background of image by the black mask
imgRBG.copyTo(imgDisplayB, maskB);
//Clone the background image
cv::Mat overlay = background.clone();
//Create ROI
cv::Mat overlayROI = overlay(cv::Rect(0,0,imgDisplayB.cols,imgDisplayB.rows));
//Replace the area which will be human image by white color
overlayROI.copyTo(imgResult, maskW);
//Add the person image
cv::addWeighted(imgResult,1,imgDisplayB,1,0.0,imgResult);
imshow("Image Result", imgResult);
waitKey();
return 0;
Check this project
https://sourceforge.net/projects/cvchromakey
void chromakey(const Mat under, const Mat over, Mat *dst, const Scalar& color) {
// Create the destination matrix
*dst = Mat(under.rows,under.cols,CV_8UC3);
for(int y=0; y<under.rows; y++) {
for(int x=0; x<under.cols; x++) {
if (over.at<Vec3b>(y,x)[0] >= red_l && over.at<Vec3b>(y,x)[0] <= red_h && over.at<Vec3b>(y,x)[1] >= green_l && over.at<Vec3b>(y,x)[1] <= green_h && over.at<Vec3b>(y,x)[2] >= blue_l && over.at<Vec3b>(y,x)[2] <= blue_h)
{
dst->at<Vec3b>(y,x)[0]= under.at<Vec3b>(y,x)[0];
dst->at<Vec3b>(y,x)[1]= under.at<Vec3b>(y,x)[1];
dst->at<Vec3b>(y,x)[2]= under.at<Vec3b>(y,x)[2];}
else{
dst->at<Vec3b>(y,x)[0]= over.at<Vec3b>(y,x)[0];
dst->at<Vec3b>(y,x)[1]= over.at<Vec3b>(y,x)[1];
dst->at<Vec3b>(y,x)[2]= over.at<Vec3b>(y,x)[2];}
}
}
}
If you know that the background is blue, you are losing valuable information by converting the image to B/W.
If the person is not wearing blue (at least not one that is very close to the background color), you don't have to use contours. just replace the blue pixels with the pixels from the other image. You can use cvScalar data type with, cvGet2D and cvSet2D functions to achieve this.
Edit:
Your code looks a lot more complicated than the original problem you stated. Having a blue background (also called "blue screen" and "chroma key") is a common method used by TV channels to change backgrounds of news readers. The reason for selecting blue was that the human skin has less dominance in the blue component.
Assuming that the person is not wearing blue, the following code should work. Let me know if you need something different.
//Read the image with person
IplImage* imgPerson = cvLoadImage("person.jpg");
//Read the image with background
IplImage* imgBackground = cvLoadImage("paris.jpg");
// assume that the blue background is quite even
// here is a possible range of pixel values
// note that I did not use all of them :-)
unsigned char backgroundRedMin = 0;
unsigned char backgroundRedMax = 10;
unsigned char backgroundGreenMin = 0;
unsigned char backgroundGreenMax = 10;
unsigned char backgroundBlueMin = 245;
unsigned char backgroundBlueMax = 255;
// for simplicity, I assume that both images are of the same resolution
// run a loop to replace pixels
for (int i=0; i<imgPerson->width; i++)
{
for (int j=0; j< imgPerson->height; j++)
{
CvScalar currentPixel = cvGet2D(imgPerson, j, i);
// compare the RGB values of the pixel, with the range
if (curEdgePixel.val[0] > backgroundBlueMin && curEdgePixel.val[1] <
backgroundGreenMax && curEdgePixel.val[2] < backgroundRedMax)
{
// copy the corresponding pixel from background
CvScalar currentBackgroundPixel = cvGet2D(imgBackground, j, i);
cvSet2D(imgPerson, j, i, currentBackgroundPixel);
}
}
}
imshow("Image Result", imgPerson);
waitKey();
return 0;

Why are spots left when converting an RGBA image to RGB?

I converted a png (RGBA) to jpeg (RGB) using libpng to decode the png file and applying png_set_strip_alpha to ignore alpha channels. But after conversion the output image has many spots. I think the reason is that the original image has areas whose alpha was 0, which hides the pixel regardless of its RGB value. And when I strip alpha(ie set alpha = 1), the pixel shows. So I think just using png_set_strip_alpha is not the right solution. Should I write a method myself, or is there already a way to achieve this in libpng?
There is no method for that. If you drop alpha channel libpng will give you raw RGB channels and this will "uncover" colors that were previously invisible.
You should load RGBA image and convert it to RGB yourself. The simplest way is to multiply RGB values by alpha.
This will convert RGBA bitmap to RGB in-place:
for(int i=0; i < width*height; i++) {
int r = bitmap[i*4+0],
g = bitmap[i*4+1],
b = bitmap[i*4+2],
a = bitmap[i*4+3];
bitmap[i*3+0] = r * a / 255;
bitmap[i*3+1] = g * a / 255;
bitmap[i*3+2] = b * a / 255;
}

How to convert an 8-bit OpenCV IplImage* to a 32-bit IplImage*?

I need to convert an 8-bit IplImage to a 32-bits IplImage. Using documentation from all over the web I've tried the following things:
// general code
img2 = cvCreateImage(cvSize(img->width, img->height), 32, 3);
int height = img->height;
int width = img->width;
int channels = img->nChannels;
int step1 = img->widthStep;
int step2 = img2->widthStep;
int depth1 = img->depth;
int depth2 = img2->depth;
uchar *data1 = (uchar *)img->imageData;
uchar *data2 = (uchar *)img2->imageData;
for(h=0;h<height;h++) for(w=0;w<width;w++) for(c=0;c<channels;c++) {
// attempt code...
}
// attempt one
// result: white image, two red spots which appear in the original image too.
// this is the closest result, what's going wrong?!
// see: http://files.dazjorz.com/cache/conversion.png
((float*)data2+h*step2+w*channels+c)[0] = data1[h*step1+w*channels+c];
// attempt two
// when I change float to unsigned long in both previous examples, I get a black screen.
// attempt three
// result: seemingly random data to the top of the screen.
data2[h*step2+w*channels*3+c] = data1[h*step1+w*channels+c];
data2[h*step2+w*channels*3+c+1] = 0x00;
data2[h*step2+w*channels*3+c+2] = 0x00;
// and then some other things. Nothing did what I wanted. I couldn't get an output
// image which looked the same as the input image.
As you see I don't really know what I'm doing. I'd love to find out, but I'd love it more if I could get this done correctly.
Thanks for any help I get!
The function you are looking for is cvConvertScale(). It automagically does any type conversion for you. You just have to specify that you want to scale by a factor of 1/255 (which maps the range [0...255] to [0...1]).
Example:
IplImage *im8 = cvLoadImage(argv[1]);
IplImage *im32 = cvCreateImage(cvSize(im8->width, im8->height), 32, 3);
cvConvertScale(im8, im32, 1/255.);
Note the dot in 1/255. - to force a double division. Without it you get a scale of 0.
Perhaps this link can help you?
Edit In response to the second edit of the OP and the comment
Have you tried
float value = 0.5
instead of
float value = 0x0000001;
I thought the range for a float color value goes from 0.0 to 1.0, where 1.0 is white.
Floating point colors go from 0.0 to 1.0, and uchars go from 0 to 255. The following code fixes it:
// h is height, w is width, c is current channel (0 to 2)
int b = ((uchar *)(img->imageData + h*img->widthStep))[w*img->nChannels + c];
((float *)(img2->imageData + h*img2->widthStep))[w*img2->nChannels + c] = ((float)b) / 255.0;
Many, many thanks to Stefan Schmidt for helping me fix this!
If you do not put the dot (.), some compilers will understand is as an int division, giving you a int result (zero in this case).
You can create an IplImage wrapper using boost::shared_ptr and template-metaprogramming. I have done that, and I get automatic garbage collection, together with automatic image conversions from one depth to another, or from one-channel to multi-channel images.
I have called the API blImageAPI and it can be found here:
http://www.barbato.us/2010/10/14/image-data-structure-based-shared_ptr-iplimage/
It is very fast, and make code very readable, (good for maintaining algorithms)
It is also can be used instead of IplImage in opencv algorithms without changing anything.
Good luck and have fun writing algorithms!!!
IplImage *img8,*img32;
img8 =cvLoadImage("a.jpg",1);
cvNamedWindow("Convert",1);
img32 = cvCreateImage(cvGetSize(img8),IPL_DEPTH_32F,3);
cvConvertScale(img8,img32,1.0/255.0,0.0);
//For Confirmation Check the pixel values (between 0 - 1)
for(int row = 0; row < img32->height; row++ ){
float* pt = (float*) (img32->imageData + row * img32->widthStep);
for ( int col = 0; col < width; col++ )
printf("\n %3.3f , %3.3f , %3.3f ",pt[3*col],pt[3*col+1],pt[3*col+2]);
}
cvShowImage("Convert",img32);
cvWaitKey(0);
cvReleaseImage(&img8);
cvReleaseImage(&img32);
cvDestroyWindow("Convert");

Resources