Hue to wavelength mapping - matlab

Is there an algorithm to find out the wavelength of the color given the hue value (between 0 degree to 360 degree). Is there any built-in function in MATLABfor the same?

While Mark Ransom and Franco Callari are completely right that you cannot recover the spectrum of a perceptual color, nor unambiguously map hue values to wavelengths, you could definitely piece something together if you just want the corresponding monochromatic wavelength.
The part of the hue cycle between 270 and 360 is another problem. There is nothing corresponding to pink or magenta in the light spectrum, so let's assume that we only use hue values between 0 and 270 degrees.
Estimating that the usable part of the visible spectrum is 400-650nm, with wavelength L (in nm) and hue value H (in degrees), you can improvise this:
L = 650 - 250 / 270 * H
650 is the maximum wavelength, 250 is the wavelength range and 270 is the hue range.
I think this should be in the right direction but there may of course be room for improvement. You might be able to get better results comparing between input hues and corresponding colors on a visible spectrum chart, and then adjusting the values somewhat.

I cant provide simple solution, but there is something you need to consider:
The visible part of the spektrum is roughly between 380nm (UV-border) and 780nm (IR-border). But what you see (hue) depends on the cone-cells triggered. Above 660nm, the M-cone is not triggered at all, so everything between 660nm and 780nm is hue 0°.
at 580nm you have yellow with hue 60°, the purest green is at about 535nm, so that is 120°, and the purest blue (240°) is at about 457nm.
if you apply a linear function, yellow should be at 597nm - which it is not, so you'd need a more complex approach.
above blue, the red cone still gets triggered until we see violet, but we wont reach red again on higher frequencies, so you cant go above approximately 300°.
the hue range between 300° and 360° has no æquivalent in visible spektrum, it can only be simulated by mixing high frequency light (blue or violet) with red light, which results in something between magenta and red on the purple-line.

It is possible to find the dominant wavelength of a color/hue. But as said most colors arn’t monochromatic and the same color can be constructed with different “mixes” of wavelengths. I.e. metamerism.
Also, for the extra spectral magenta and violet colors only a complementary wavelength can be specified. I.e. the hue/dominant wavelength that additively mixes to white. Also white must be specified, since the is no absolute white due to adaption.
Also psychologically our perception of hues doesn’t follow dominant hue lines. Se the Munsell and NCS systems.
Here you can calulate dominant wavelength from RGB values or different CIE systems: http://www.brucelindbloom.com/index.html?Calc.html
I don’t have the formula though.
You can then transform RGB to/from HSL and similar. And to/from Munsell or NCS perceptual hues (NCS values are proprietary, so you have to pay and use their software).

Short answer: NO. A given hue can in general be produced by a triple infinity of wavelengths.
A "physical color" is a combination of pure spectral colors (in the visible range). In principle there exist infinitely many distinct spectral colors, and so the set of all physical colors may be thought of as an infinite-dimensional vector space (a Hilbert space). This space is typically notated Hcolor. More technically, the space of physical colors may be considered to be the topological cone over the simplex whose vertices are the spectral colors, with white at the centroid of the simplex, black at the apex of the cone, and the monochromatic color associated with any given vertex somewhere along the line from that vertex to the apex depending on its brightness.
. . .
This system implies that for any hue or non-spectral color not on the boundary of the chromaticity diagram, there are infinitely many distinct physical spectra that are all perceived as that hue or color. So, in general there is no such thing as the combination of spectral colors that we perceive as (say) a specific version of tan; instead there are infinitely many possibilities that produce that exact color. The boundary colors that are pure spectral colors can be perceived only in response to light that is purely at the associated wavelength, while the boundary colors on the "line of purples" can each only be generated by a specific ratio of the pure violet and the pure red at the ends of the visible spectral colors.
The CIE chromaticity diagram is horseshoe-shaped, with its curved edge corresponding to all spectral colors (the spectral locus), and the remaining straight edge corresponding to the most saturated purples, mixtures of red and violet.
(Source)

I found this site that converts a given wavelength to a hue. With a bit of work, you could actually reverse the process. It's not ideal, but I trust the guy who is a consultant in applied mathematics more than myself in solving this issue. That's that.
https://www.johndcook.com/wavelength_to_RGB.html
function convert(input) {
var w = parseFloat(input);
if (w >= 380 && w < 440) {
r = -(w - 440) / (440 - 380);
g = 0.0;
b = 1.0;
} else if (w >= 440 && w < 490) {
r = 0.0;
g = (w - 440) / (490 - 440);
b = 1.0;
} else if (w >= 490 && w < 510) {
r = 0.0;
g = 1.0;
b = -(w - 510) / (510 - 490);
} else if (w >= 510 && w < 580) {
r = (w - 510) / (580 - 510);
g = 1.0;
b = 0.0;
} else if (w >= 580 && w < 645) {
r = 1.0;
g = -(w - 645) / (645 - 580);
b = 0.0;
} else if (w >= 645 && w < 781) {
r = 1.0;
g = 0.0;
b = 0.0;
} else {
r = 0.0;
g = 0.0;
b = 0.0;
}
// Let the intensity fall off near the vision limits
if (w >= 380 && w < 420)
factor = 0.3 + 0.7 * (w - 380) / (420 - 380);
else if (w >= 420 && w < 701)
factor = 1.0;
else if (w >= 701 && w < 781)
factor = 0.3 + 0.7 * (780 - w) / (780 - 700);
else
factor = 0.0;
var gamma = 0.80;
var R = (r > 0 ? 255 * Math.pow(r * factor, gamma) : 0);
var G = (g > 0 ? 255 * Math.pow(g * factor, gamma) : 0);
var B = (b > 0 ? 255 * Math.pow(b * factor, gamma) : 0);
return [R, G, B]
}

There's no conversion because they don't overlap.
Hue moves you around an RGB colour space, usually sRGB that almost all consumer digital equipment uses. That's a subset of the colours that our visual systems recognise under normal conditions (defined by CIE 1931), and does not overlap the vibrant line of colours perceived at monochromatic wavelengths of light at all.
Though Hue from 0-120 (reddish orange to yellowish green) and near 240 (indigo) are reasonably close, sRGB is quite functional if you don't care about all the washed out greens and blues, and you can fake the violet and red ends of the full spectrum by making them darker Hue around 270 or 330 respectively, and the only place you can't really approximate is around 180, computer cyan just isn't close at all to the monochromatic vibrant blue-greens.

Related

Can I change a color within an image in Swift [duplicate]

My question is if I have a Lion image just I want to change the color of the lion alone not the background color. For that I referred this SO question but it turns the color of whole image. Moreover the image is not looking great. I need the color change like photoshop. whether it is possible to do this in coregraphics or I have to use any other library.
EDIT : I need the color change to be like iQuikColor app
This took quite a while to figure out, mainly because I wanted to get it up and running in Swift using Core Image and CIColorCube.
#Miguel's explanation is spot on about the way you need to replace a "Hue angle range" with another "Hue angle range". You can read his post above for details on what a Hue Angle Range is.
I made a quick app that replaces a default blue truck below, with whatever you choose on Hue slider.
You can slide the slider to tell the app what color Hue you want to replace the blue with.
I'm hardcoding the Hue range to be 60 degrees, which typically seems to encompass most of a particular color but you can edit that if you need to.
Notice that it does not color the tires or the tail lights because that's outside of the 60 degree range of the truck's default blue hue, but it does handle shading appropriately.
First you need code to convert RGB to HSV (Hue value):
func RGBtoHSV(r : Float, g : Float, b : Float) -> (h : Float, s : Float, v : Float) {
var h : CGFloat = 0
var s : CGFloat = 0
var v : CGFloat = 0
let col = UIColor(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: 1.0)
col.getHue(&h, saturation: &s, brightness: &v, alpha: nil)
return (Float(h), Float(s), Float(v))
}
Then you need to convert HSV to RGB. You want to use this when you discover a hue that in your desired hue range (aka, a color that's the same blue hue of the default truck) to save off any adjustments you make.
func HSVtoRGB(h : Float, s : Float, v : Float) -> (r : Float, g : Float, b : Float) {
var r : Float = 0
var g : Float = 0
var b : Float = 0
let C = s * v
let HS = h * 6.0
let X = C * (1.0 - fabsf(fmodf(HS, 2.0) - 1.0))
if (HS >= 0 && HS < 1) {
r = C
g = X
b = 0
} else if (HS >= 1 && HS < 2) {
r = X
g = C
b = 0
} else if (HS >= 2 && HS < 3) {
r = 0
g = C
b = X
} else if (HS >= 3 && HS < 4) {
r = 0
g = X
b = C
} else if (HS >= 4 && HS < 5) {
r = X
g = 0
b = C
} else if (HS >= 5 && HS < 6) {
r = C
g = 0
b = X
}
let m = v - C
r += m
g += m
b += m
return (r, g, b)
}
Now you simply loop through a full RGBA color cube and "adjust" any colors in the "default blue" hue range with those from your newly desired hue. Then use Core Image and the CIColorCube filter to apply your adjusted color cube to the image.
func render() {
let centerHueAngle: Float = 214.0/360.0 //default color of truck body blue
let destCenterHueAngle: Float = slider.value
let minHueAngle: Float = (214.0 - 60.0/2.0) / 360 //60 degree range = +30 -30
let maxHueAngle: Float = (214.0 + 60.0/2.0) / 360
var hueAdjustment = centerHueAngle - destCenterHueAngle
let size = 64
var cubeData = [Float](count: size * size * size * 4, repeatedValue: 0)
var rgb: [Float] = [0, 0, 0]
var hsv: (h : Float, s : Float, v : Float)
var newRGB: (r : Float, g : Float, b : Float)
var offset = 0
for var z = 0; z < size; z++ {
rgb[2] = Float(z) / Float(size) // blue value
for var y = 0; y < size; y++ {
rgb[1] = Float(y) / Float(size) // green value
for var x = 0; x < size; x++ {
rgb[0] = Float(x) / Float(size) // red value
hsv = RGBtoHSV(rgb[0], g: rgb[1], b: rgb[2])
if hsv.h < minHueAngle || hsv.h > maxHueAngle {
newRGB.r = rgb[0]
newRGB.g = rgb[1]
newRGB.b = rgb[2]
} else {
hsv.h = destCenterHueAngle == 1 ? 0 : hsv.h - hueAdjustment //force red if slider angle is 360
newRGB = HSVtoRGB(hsv.h, s:hsv.s, v:hsv.v)
}
cubeData[offset] = newRGB.r
cubeData[offset+1] = newRGB.g
cubeData[offset+2] = newRGB.b
cubeData[offset+3] = 1.0
offset += 4
}
}
}
let data = NSData(bytes: cubeData, length: cubeData.count * sizeof(Float))
let colorCube = CIFilter(name: "CIColorCube")!
colorCube.setValue(size, forKey: "inputCubeDimension")
colorCube.setValue(data, forKey: "inputCubeData")
colorCube.setValue(ciImage, forKey: kCIInputImageKey)
if let outImage = colorCube.outputImage {
let context = CIContext(options: nil)
let outputImageRef = context.createCGImage(outImage, fromRect: outImage.extent)
imageView.image = UIImage(CGImage: outputImageRef)
}
}
You can download the sample project here.
See answers below instead. Mine doesn't provide a complete solution.
Here is the sketch of a possible solution using OpenCV:
Convert the image from RGB to HSV using cvCvtColor (we only want to change the hue).
Isolate a color with cvThreshold specifying a certain tolerance (you want a range of colors, not one flat color).
Discard areas of color below a minimum size using a blob detection library like cvBlobsLib. This will get rid of dots of the similar color in the scene.
Mask the color with cvInRangeS and use the resulting mask to apply the new hue.
cvMerge the new image with the new hue with an image composed by the saturation and brightness channels that you saved in step one.
There are several OpenCV iOS ports in the net, eg: http://www.eosgarden.com/en/opensource/opencv-ios/overview/ I haven't tried this myself, but seems a good research direction.
I'm going to make the assumption that you know how to perform these basic operations, so these won't be included in my solution:
load an image
get the RGB value of a given pixel of the loaded image
set the RGB value of a given pixel
display a loaded image, and/or save it back to disk.
First of all, let's consider how you can describe the source and destination colors. Clearly you can't specify these as exact RGB values, since a photo will have slight variations in color. For example, the green pixels in the truck picture you posted are not all exactly the same shade of green. The RGB color model isn't very good at expressing basic color characteristics, so you will get much better results if you convert the pixels to HSL. Here are C functions to convert RGB to HSL and back.
The HSL color model describes three aspects of a color:
Hue - the main perceived color - i.e. red, green, orange, etc.
Saturation - how "full" the color is - i.e. from full color to no color at all
Lightness - how bright the color is
So for example, if you wanted to find all the green pixels in a picture, you will convert each pixel from RGB to HSL, then look for H values that correspond to green, with some tolerance for "near green" colors. Below is a Hue chart, from Wikipedia:
So in your case you will be looking at pixels that have a Hue of 120 degrees +/- some amount. The bigger the range the more colors will get selected. If you make your range too wide you will start seeing yellow and cyan pixels getting selected, so you'll have to find the right range, and you may even want to offer the user of your app controls to select this range.
In addition to selecting by Hue, you may want to allow ranges for Saturation and Lightness, so that you can optionally put more limits to the pixels that you want to select for colorization.
Finally, you may want to offer the user the ability to draw a "lasso selection" so that specific parts of the picture can be left out of the colorization. This is how you could tell the app that you want the body of the green truck, but not the green wheel.
Once you know which pixels you want to modify it's time to alter their color.
The easiest way to colorize the pixels is to just change the Hue, leaving the Saturation and Lightness from the original pixel. So for example, if you want to make green pixels magenta you will be adding 180 degrees to all the Hue values of the selected pixels (making sure you use modulo 360 math).
If you wanted to get more sophisticated, you can also apply changes to Saturation and that will give you a wider range of tones you can go to. I think the Lightness is better left alone, you may be able to make small adjustments and the image will still look good, but if you go too far away from the original you may start seeing hard edges where the process pixels border with background pixels.
Once you have the colorized HSL pixel you just convert it back to RGB and write it back to the image.
I hope this helps. A final comment I should make is that Hue values in code are typically recorded in the 0-255 range, but many applications show them as a color wheel with a range of 0 to 360 degrees. Keep that in mind!
Can I suggest you look into using OpenCV? It's an open source image manipulation library, and it's got an iOS port too. There are plenty of blog posts about how to use it and set it up.
It has a whole heap of functions that will help you do a good job of what you're attempting. You could do it just using CoreGraphics, but the end result isn't going to look nearly as good as OpenCV would.
It was developed by some folks at MIT, so as you might expect it does a pretty good job at things like edge detection and object tracking. I remember reading a blog about how to separate a certain color from a picture with OpenCV - the examples showed a pretty good result. See here for an example. From there I can't imagine it would be a massive job to actually change the separated color to something else.
I don't know of a CoreGraphics operation for this, and I don't see a suitable CoreImage filter for this. If that's correct, then here's a push in the right direction:
Assuming you have a CGImage (or a uiImage.CGImage):
Begin by creating a new CGBitmapContext
Draw the source image to the bitmap context
Get a handle to the bitmap's pixel data
Learn how the buffer is structured so you could properly populate a 2D array of pixel values which have the form:
typedef struct t_pixel {
uint8_t r, g, b, a;
} t_pixel;
Then create the color to locate:
const t_pixel ColorToLocate = { 0,0,0,255 }; // << black, opaque
And its substitution value:
const t_pixel SubstitutionColor = { 255,255,255,255 }; // << white, opaque
Iterate over the bitmap context's pixel buffer, creating t_pixels.
When you find a pixel which matches ColorToLocate, replace the source values with the values in SubstitutionColor.
Create a new CGImage from the CGBitmapContext.
That's the easy part! All that does is takes a CGImage, replace exact color matches, and produces a new CGImage.
What you want is more sophisticated. For this task, you will want a good edge detection algorithm.
I've not used this app you have linked. If it's limited to a few colors, then they may simply be swapping channel values, paired with edge detection (keep in mind that buffers may also be represented in multiple color models - not just RGBA).
If (in the app you linked) the user can choose an arbitrary colors, values, and edge thresholds, then you will have to use real blending and edge detection. If you need to see how this is accomplished, you may want to check out a package such as Gimp (it's an open image editor) - they have the algos to detect edges and choose by color.

Trouble with the assignment of values to pixels

I'm currently trying to write a function in MatLab which loops over each pixel, takes the mean intensity of the pixels within a radius around it and then applies that intensity to the central pixel, effectively blurring the image.
I start by declaring the function and finding the maximum width and height of the image, nx and ny:
function [] = immean(IMAGE, r)
[nx, ny] = size(IMAGE);
I then create a completely black image of the same size as the image variable IMAGE. This is so that I can store the value of each pixel, once the mean intensity of its neighbourhood has been found.
average = zeros(size(IMAGE));
I then loop through the image:
for x = 1:nx
for y = 1:ny
and apply a series of if-statements to deal with cases where the radius of the circle around the pixel does not fit the image. (For example, a pixel at (1,1) with a radius of 5 would have a starting point of -4, which would cause an error):
if x-r <= 0
startx = 1;
else
startx = x-r;
end
if x+r > nx
endx = nx;
else
endx = x+r;
end
if y-r <= 0
starty = 1;
else
starty = y-r;
end
if y+r > ny
endy = ny;
else
endy = y+r;
end
This effectively creates a square of values that may fall under the domain of the circular sample, which speeds up the program dramatically. After that, I iterate through the values within this square and find any pixels which fall within the radius of the central pixel. The intensities of these pixels are then added to a variable called total and the count pixelcount increments:
total = 0;
pixelcount = 0;
for xp = startx : endx
for yp = starty : endy
if (x-xp)^2 + (y-yp)^2 <= r^2
total = total + uint64(IMAGE(xp, yp));
pixelcount = pixelcount + 1;
end
end
end
I then find the mean intensity of the circular sample of pixels, by dividing total by pixelcount and then plug that value into the appropriate pixel of the completely black image average:
mean = total / pixelcount;
average(x,y) = mean;
The trouble is: this isn't working. Instead of a blurred version of the original image, I get an entirely white image instead. I'm not sure why - when I take the ; from the last line, it shows me that mean constitutes many values - it's not like they're all 255. So I figure that there must be something wrong with the assignment line average(x,y) = mean;, but I can't find out what that is.
Can anyone see why this is going wrong?

Extract black objects from color background

It is easy for human eyes to tell black from other colors. But how about computers?
I printed some color blocks on the normal A4 paper. Since there are three kinds of ink to compose a color image, cyan, magenta and yellow, I set the color of each block C=20%, C=30%, C=40%, C=50% and rest of two colors are 0. That is the first column of my source image. So far, no black (K of CMYK) ink is supposed to print. After that, I set the color of each dot K=100% and rest colors are 0 to print black dots.
You may feel my image is weird and awful. In fact, the image is magnified 30 times and how the ink cheat our eyes can be seen clearly. The color strips hamper me to recognize these black dots (the dot is printed as just one pixel in 800 dpi). Without the color background, I used to blur and do canny edge detector to extract the edge. However, when adding color background, simply do grayscale and edge detector cannot get good results because of the strips. How will my eyes do in order to solve such problems?
I determined to check the brightness of source image. I referred this article and formula:
brightness = sqrt( 0.299 R * R + 0.587 G * G + 0.114 B * B )
The brightness is more close to human perception and it works very well in the yellow background because the brightness of yellow is the highest compared with cyan and magenta. But how to make cyan and magenta strips as bright as possible? The expected result is that all the strips disappear.
More complicated image:
C=40%, M=40%
C=40%, Y=40%
Y=40%, M=40%
FFT result of C=40%, Y=40% brightness image
Anyone can give me some hints to remove the color strips?
#natan I tried FFT method you suggested me, but I was not lucky to get peak at both axis x and y. In order to plot the frequency as you did, I resized my image to square.
I would convert the image to the HSV colour space and then use the Value channel. This basically separates colour and brightness information.
This is the 50% cyan image
Then you can just do a simple threshold to isolate the dots.
I just did this very quickly and im sure you could get better results. Maybe find contours in the image and then remove any contours with a small area, to filter any remaining noise.
After inspecting the images, I decided that a robust threshold will be more simple than anything. For example, looking at the C=40%, M=40% photo, I first inverted the intensities so black (the signal) will be white just using
im=(abs(255-im));
we can inspect its RGB histograms using this :
hist(reshape(single(im),[],3),min(single(im(:))):max(single(im(:))));
colormap([1 0 0; 0 1 0; 0 0 1]);
so we see that there is a large contribution to some middle intensity whereas the "signal" which is now white, is mostly separated to higher value. I then applied a simple thresholds as follows:
thr = #(d) (max([min(max(d,[],1)) min(max(d,[],2))])) ;
for n=1:size(im,3)
imt(:,:,n)=im(:,:,n).*uint8(im(:,:,n)>1.1*thr(im(:,:,n)));
end
imt=rgb2gray(imt);
and got rid of objects smaller than some typical area size
min_dot_area=20;
bw=bwareaopen(imt>0,min_dot_area);
imagesc(bw);
colormap(flipud(bone));
here's the result together with the original image:
The origin of this threshold is from this code I wrote that assumed sparse signals in the form of 2-D peaks or blobs in a noisy background. By sparse I meant that there's no pile up of peaks. In that case, when projecting max(image) on the x or y axis (by (max(im,[],1) or (max(im,[],1) you get a good measure of the background. That is because you take the minimal intensity of the max(im) vector.
If you want to look at this differently you can look at the histogram of the intensities of the image. The background is supposed to be a normal distribution of some kind around some intensity, the signal should be higher than that intensity, but with much lower # of occurrences. By finding max(im) of one of the axes (x or y) you discover what was the maximal noise level.
You'll see that the threshold picks that point in the histogram where there are still some noise above it, but ALL the signal is above it too. that's why I adjusted it to be 1.1*thr. Last, there are many fancier ways to obtain a robust threshold, this is a quick and dirty way that in my view is good enough...
Thanks to everyone for posting his answer! After some search and attempt, I also come up with an adaptive method to extract these black dots from the color background. It seems that considering only the brightness could not solve the problem perfectly. Therefore natan's method which calculates and analyzes the RGB histogram is more robust. Unfortunately, I still cannot obtain a robust threshold to extract the black dots in other color samples, because things are getting more and more unpredictable when we add deeper color (e.g. Cyan > 60) or mix two colors together (e.g. Cyan = 50, Magenta = 50).
One day, I google "extract color" and TinEye's color extraction and color thief inspire me. Both of them are very cool application and the image processed by the former website is exactly what I want. So I determine to implement a similar stuff on my own. The algorithm I used here is k-means clustering. And some other related key words to search may be color palette, color quantation and getting dominant color.
I firstly apply Gaussian filter to smooth the image.
GaussianBlur(img, img, Size(5, 5), 0, 0);
OpenCV has kmeans function and it saves me a lot of time on coding. I modify this code.
// Input data should be float32
Mat samples(img.rows * img.cols, 3, CV_32F);
for (int i = 0; i < img.rows; i++) {
for (int j = 0; j < img.cols; j++) {
for (int z = 0; z < 3; z++) {
samples.at<float>(i + j * img.rows, z) = img.at<Vec3b>(i, j)[z];
}
}
}
// Select the number of clusters
int clusterCount = 4;
Mat labels;
int attempts = 1;
Mat centers;
kmeans(samples, clusterCount, labels, TermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 10, 0.1), attempts, KMEANS_PP_CENTERS, centers);
// Draw clustered result
Mat cluster(img.size(), img.type());
for (int i = 0; i < img.rows; i++) {
for(int j = 0; j < img.cols; j++) {
int cluster_idx = labels.at<int>(i + j * img.rows, 0);
cluster.at<Vec3b>(i, j)[0] = centers.at<float>(cluster_idx, 0);
cluster.at<Vec3b>(i, j)[1] = centers.at<float>(cluster_idx, 1);
cluster.at<Vec3b>(i, j)[2] = centers.at<float>(cluster_idx, 2);
}
}
imshow("clustered image", cluster);
// Check centers' RGB value
cout << centers;
After clustering, I convert the result to grayscale and find the darkest color which is more likely to be the color of the black dots.
// Find the minimum value
cvtColor(cluster, cluster, CV_RGB2GRAY);
Mat dot = Mat::zeros(img.size(), CV_8UC1);
cluster.copyTo(dot);
int minVal = (int)dot.at<uchar>(dot.cols / 2, dot.rows / 2);
for (int i = 0; i < dot.rows; i += 3) {
for (int j = 0; j < dot.cols; j += 3) {
if ((int)dot.at<uchar>(i, j) < minVal) {
minVal = (int)dot.at<uchar>(i, j);
}
}
}
inRange(dot, minVal - 5 , minVal + 5, dot);
imshow("dot", dot);
Let's test two images.
(clusterCount = 4)
(clusterCount = 5)
One shortcoming of the k-means clustering is one fixed clusterCount cannot be applied to every image. Also clustering is not so fast for larger images. That's the issue annoys me a lot. My dirty method for better real time performance (on iPhone) is to crop 1/16 of the image and cluster the smaller area. Then compare all the pixels in the original image with each cluster center, and pick the pixel that are the nearest to the "black" color. I simply calculate euclidean distance between two RGB colors.
A simple method is to just threshold all the pixels. Here is this idea expressed in pseudo code.
for each pixel in image
if brightness < THRESHOLD
pixel = BLACK
else
pixel = WHITE
Or if you're always dealing with cyan, magenta and yellow backgrounds then maybe you might get better results with the criteria
if pixel.r < THRESHOLD and pixel.g < THRESHOLD and pixel.b < THRESHOLD
This method will only give good results for easy images where nothing except the black dots is too dark.
You can experiment with the value of THRESHOLD to find a good value for your images.
I suggest to convert to some chroma-based color space, like LCH, and adjust simultaneous thresholds on lightness and chroma. Here is the result mask for L < 50 & C < 25 for the input image:
Seems like you need adaptive thresholds since different values work best for different areas of the image.
You may also use HSV or HSL as a color space, but they are less perceptually uniform than LCH, derived from Lab.

How to change a particular color in an image?

My question is if I have a Lion image just I want to change the color of the lion alone not the background color. For that I referred this SO question but it turns the color of whole image. Moreover the image is not looking great. I need the color change like photoshop. whether it is possible to do this in coregraphics or I have to use any other library.
EDIT : I need the color change to be like iQuikColor app
This took quite a while to figure out, mainly because I wanted to get it up and running in Swift using Core Image and CIColorCube.
#Miguel's explanation is spot on about the way you need to replace a "Hue angle range" with another "Hue angle range". You can read his post above for details on what a Hue Angle Range is.
I made a quick app that replaces a default blue truck below, with whatever you choose on Hue slider.
You can slide the slider to tell the app what color Hue you want to replace the blue with.
I'm hardcoding the Hue range to be 60 degrees, which typically seems to encompass most of a particular color but you can edit that if you need to.
Notice that it does not color the tires or the tail lights because that's outside of the 60 degree range of the truck's default blue hue, but it does handle shading appropriately.
First you need code to convert RGB to HSV (Hue value):
func RGBtoHSV(r : Float, g : Float, b : Float) -> (h : Float, s : Float, v : Float) {
var h : CGFloat = 0
var s : CGFloat = 0
var v : CGFloat = 0
let col = UIColor(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: 1.0)
col.getHue(&h, saturation: &s, brightness: &v, alpha: nil)
return (Float(h), Float(s), Float(v))
}
Then you need to convert HSV to RGB. You want to use this when you discover a hue that in your desired hue range (aka, a color that's the same blue hue of the default truck) to save off any adjustments you make.
func HSVtoRGB(h : Float, s : Float, v : Float) -> (r : Float, g : Float, b : Float) {
var r : Float = 0
var g : Float = 0
var b : Float = 0
let C = s * v
let HS = h * 6.0
let X = C * (1.0 - fabsf(fmodf(HS, 2.0) - 1.0))
if (HS >= 0 && HS < 1) {
r = C
g = X
b = 0
} else if (HS >= 1 && HS < 2) {
r = X
g = C
b = 0
} else if (HS >= 2 && HS < 3) {
r = 0
g = C
b = X
} else if (HS >= 3 && HS < 4) {
r = 0
g = X
b = C
} else if (HS >= 4 && HS < 5) {
r = X
g = 0
b = C
} else if (HS >= 5 && HS < 6) {
r = C
g = 0
b = X
}
let m = v - C
r += m
g += m
b += m
return (r, g, b)
}
Now you simply loop through a full RGBA color cube and "adjust" any colors in the "default blue" hue range with those from your newly desired hue. Then use Core Image and the CIColorCube filter to apply your adjusted color cube to the image.
func render() {
let centerHueAngle: Float = 214.0/360.0 //default color of truck body blue
let destCenterHueAngle: Float = slider.value
let minHueAngle: Float = (214.0 - 60.0/2.0) / 360 //60 degree range = +30 -30
let maxHueAngle: Float = (214.0 + 60.0/2.0) / 360
var hueAdjustment = centerHueAngle - destCenterHueAngle
let size = 64
var cubeData = [Float](count: size * size * size * 4, repeatedValue: 0)
var rgb: [Float] = [0, 0, 0]
var hsv: (h : Float, s : Float, v : Float)
var newRGB: (r : Float, g : Float, b : Float)
var offset = 0
for var z = 0; z < size; z++ {
rgb[2] = Float(z) / Float(size) // blue value
for var y = 0; y < size; y++ {
rgb[1] = Float(y) / Float(size) // green value
for var x = 0; x < size; x++ {
rgb[0] = Float(x) / Float(size) // red value
hsv = RGBtoHSV(rgb[0], g: rgb[1], b: rgb[2])
if hsv.h < minHueAngle || hsv.h > maxHueAngle {
newRGB.r = rgb[0]
newRGB.g = rgb[1]
newRGB.b = rgb[2]
} else {
hsv.h = destCenterHueAngle == 1 ? 0 : hsv.h - hueAdjustment //force red if slider angle is 360
newRGB = HSVtoRGB(hsv.h, s:hsv.s, v:hsv.v)
}
cubeData[offset] = newRGB.r
cubeData[offset+1] = newRGB.g
cubeData[offset+2] = newRGB.b
cubeData[offset+3] = 1.0
offset += 4
}
}
}
let data = NSData(bytes: cubeData, length: cubeData.count * sizeof(Float))
let colorCube = CIFilter(name: "CIColorCube")!
colorCube.setValue(size, forKey: "inputCubeDimension")
colorCube.setValue(data, forKey: "inputCubeData")
colorCube.setValue(ciImage, forKey: kCIInputImageKey)
if let outImage = colorCube.outputImage {
let context = CIContext(options: nil)
let outputImageRef = context.createCGImage(outImage, fromRect: outImage.extent)
imageView.image = UIImage(CGImage: outputImageRef)
}
}
You can download the sample project here.
See answers below instead. Mine doesn't provide a complete solution.
Here is the sketch of a possible solution using OpenCV:
Convert the image from RGB to HSV using cvCvtColor (we only want to change the hue).
Isolate a color with cvThreshold specifying a certain tolerance (you want a range of colors, not one flat color).
Discard areas of color below a minimum size using a blob detection library like cvBlobsLib. This will get rid of dots of the similar color in the scene.
Mask the color with cvInRangeS and use the resulting mask to apply the new hue.
cvMerge the new image with the new hue with an image composed by the saturation and brightness channels that you saved in step one.
There are several OpenCV iOS ports in the net, eg: http://www.eosgarden.com/en/opensource/opencv-ios/overview/ I haven't tried this myself, but seems a good research direction.
I'm going to make the assumption that you know how to perform these basic operations, so these won't be included in my solution:
load an image
get the RGB value of a given pixel of the loaded image
set the RGB value of a given pixel
display a loaded image, and/or save it back to disk.
First of all, let's consider how you can describe the source and destination colors. Clearly you can't specify these as exact RGB values, since a photo will have slight variations in color. For example, the green pixels in the truck picture you posted are not all exactly the same shade of green. The RGB color model isn't very good at expressing basic color characteristics, so you will get much better results if you convert the pixels to HSL. Here are C functions to convert RGB to HSL and back.
The HSL color model describes three aspects of a color:
Hue - the main perceived color - i.e. red, green, orange, etc.
Saturation - how "full" the color is - i.e. from full color to no color at all
Lightness - how bright the color is
So for example, if you wanted to find all the green pixels in a picture, you will convert each pixel from RGB to HSL, then look for H values that correspond to green, with some tolerance for "near green" colors. Below is a Hue chart, from Wikipedia:
So in your case you will be looking at pixels that have a Hue of 120 degrees +/- some amount. The bigger the range the more colors will get selected. If you make your range too wide you will start seeing yellow and cyan pixels getting selected, so you'll have to find the right range, and you may even want to offer the user of your app controls to select this range.
In addition to selecting by Hue, you may want to allow ranges for Saturation and Lightness, so that you can optionally put more limits to the pixels that you want to select for colorization.
Finally, you may want to offer the user the ability to draw a "lasso selection" so that specific parts of the picture can be left out of the colorization. This is how you could tell the app that you want the body of the green truck, but not the green wheel.
Once you know which pixels you want to modify it's time to alter their color.
The easiest way to colorize the pixels is to just change the Hue, leaving the Saturation and Lightness from the original pixel. So for example, if you want to make green pixels magenta you will be adding 180 degrees to all the Hue values of the selected pixels (making sure you use modulo 360 math).
If you wanted to get more sophisticated, you can also apply changes to Saturation and that will give you a wider range of tones you can go to. I think the Lightness is better left alone, you may be able to make small adjustments and the image will still look good, but if you go too far away from the original you may start seeing hard edges where the process pixels border with background pixels.
Once you have the colorized HSL pixel you just convert it back to RGB and write it back to the image.
I hope this helps. A final comment I should make is that Hue values in code are typically recorded in the 0-255 range, but many applications show them as a color wheel with a range of 0 to 360 degrees. Keep that in mind!
Can I suggest you look into using OpenCV? It's an open source image manipulation library, and it's got an iOS port too. There are plenty of blog posts about how to use it and set it up.
It has a whole heap of functions that will help you do a good job of what you're attempting. You could do it just using CoreGraphics, but the end result isn't going to look nearly as good as OpenCV would.
It was developed by some folks at MIT, so as you might expect it does a pretty good job at things like edge detection and object tracking. I remember reading a blog about how to separate a certain color from a picture with OpenCV - the examples showed a pretty good result. See here for an example. From there I can't imagine it would be a massive job to actually change the separated color to something else.
I don't know of a CoreGraphics operation for this, and I don't see a suitable CoreImage filter for this. If that's correct, then here's a push in the right direction:
Assuming you have a CGImage (or a uiImage.CGImage):
Begin by creating a new CGBitmapContext
Draw the source image to the bitmap context
Get a handle to the bitmap's pixel data
Learn how the buffer is structured so you could properly populate a 2D array of pixel values which have the form:
typedef struct t_pixel {
uint8_t r, g, b, a;
} t_pixel;
Then create the color to locate:
const t_pixel ColorToLocate = { 0,0,0,255 }; // << black, opaque
And its substitution value:
const t_pixel SubstitutionColor = { 255,255,255,255 }; // << white, opaque
Iterate over the bitmap context's pixel buffer, creating t_pixels.
When you find a pixel which matches ColorToLocate, replace the source values with the values in SubstitutionColor.
Create a new CGImage from the CGBitmapContext.
That's the easy part! All that does is takes a CGImage, replace exact color matches, and produces a new CGImage.
What you want is more sophisticated. For this task, you will want a good edge detection algorithm.
I've not used this app you have linked. If it's limited to a few colors, then they may simply be swapping channel values, paired with edge detection (keep in mind that buffers may also be represented in multiple color models - not just RGBA).
If (in the app you linked) the user can choose an arbitrary colors, values, and edge thresholds, then you will have to use real blending and edge detection. If you need to see how this is accomplished, you may want to check out a package such as Gimp (it's an open image editor) - they have the algos to detect edges and choose by color.

iPhone colour Image analysis

I am looking for some ideas about an approach that will let me analyze an image, and determine how greenISH or brownISH or whiteISH it is... I am emphasizing ISH here because, I am interested in capturing ALL the shades of these colours. So far, I have done the following:
I have my UIImage, I have CGImageRef and I actually have the colour of the pixel itself (it's RGB and Alpha), what I don't know is how to quantify this, and determine all the green shades, blues, browns, yellows, purples etc... So, I can process each and every pixel, determine it's basic RGB, but I need some help in quantifying the colours it over a whole image.
Thanks for your ideas...
Alex.
One fairly good solution is to switch from RGB colour space to one of the Y colour spaces, such as YUV, YCrCb or any of those. In all cases the Y channel represents brightness and the other two channels together represent colour, relative to brightness. You probably want to factor brightness out, possibly with the caveat that all colours below a certain darkness are to be excluded, so getting Y separately is a helpful first step in itself.
Converting from RGB to YUV is achieved with a simple linear combination. Straight from Wikipedia and a thousand other sources:
y = 0.299*r + 0.587*g + 0.114*b;
u = -0.14713*r - 0.28886*g + 0.436*b;
v = 0.615*r - 0.51499*g - 0.10001*b;
Assuming you're keeping r, g and b in the range [0, 1], your first test might be:
if(y < 0.05)
{
// this colour is very dark, so it's considered to be as
// far as we allow from any colour we're interested in
}
To decide how close your colour then is to, say, green, work out the u and v components of the green you're interested in, as a proportion of the y:
r = b = 0;
g = 0;
y = 0.299*r + 0.587*g + 0.114*b = 0.587;
u = -0.14713*r - 0.28886*g + 0.436*b = -0.28886;
v = 0.615*r - 0.51499*g - 0.10001*b = -0.51499;
proportionOfU = u / y = -2.0479;
proportionOfV = v / y = -0.8773;
Subsequently, work out and compare the proportions of U and V for incoming colours and compare (e.g. with 2d planar distance) to those you've computed for the colour you're comparing to. Closer values are more similar. How you scale and use that metric depends on your application.
Notice that as y goes toward 0, the computed proportions become increasingly less precise because of the limited range of the input data, and are undefined when y is 0. Conceptually that's because all colours look exactly the same when there's no light on them. Checking that y is above at least a certain minimum value is the pragmatic way of working around this issue. This also means that you're not going to get sensible results if you try to say "how black is this picture?", though again that's because of the ambiguity between a surface that doesn't reflect any light and a surface that doesn't have any light falling upon it.