How to convert rgb to labels for image segmentation - python

I have around 4000 rgb label images which are masks for some other images. I can use this image label pair in the deep learning encoder-decoder structure (eg:UNet) architecture with the help of regression approach. But I would like to do segmentation approach. For that how can I convert these images?
Sample image:
(Above sample image should contain 3 classes. one oval shape part, the remaining red part, and the background white part. This can go upto 7 classes in some other image pairs)
There is supposed to be 7 classes including background for the entire dataset. But when I tried to find the unique values in an RGB label, there are more than 30 unique value pairs coming. Otherwise I would have select the unique rgb pair and do the processing. How to overcome this

Here's one potential way to handle this (in MATLAB, but similar in other situations)
The image you have shared is rather pixelated, and hence quite difficult to handle. If your dataset contains similarly pixelated images, I'd explore some kind of pre-processing to get rid of spurious edge discolorations, as they mess up the clustering. For the sake of demonstration here, I've created a test image with exactly three colors.
% Create a test image - one shared is very pixelated.
I = uint8(zeros(100, 100, 3));
I(10:20, 10:20, 1) = 255;
I(40:50, 40:50, 2) = 255;
If the number of colors here is unknown, but up to 7, here's a quick way to use imsegkmeans and it's 'C' output to find the number of unique centers.
% Specify max clusters
maxNumClusters = 7;
% Run clustering using the max value
[~, C] = imsegkmeans(I, maxNumClusters);
nUniqueClusters = size(unique(C, 'rows'), 1);
'nUniqueClusters' should now contain the 'true' number of clusters in the image. In a way, this is almost like finding the number of unique entries of pixel RGB triplets in the image itself - I think what's affecting your work is noise due to pixelation - which is a separate problem.
[L, C] = imsegkmeans(I, nUniqueClusters);
% Display the labeled image for further verification.
B = labeloverlay(I, L);
figure, imshow(B)
One way to attempt to fix the pixelation problem is to plot a histogram of your image pixels (for one of the three color planes), and then managing the low values somehow - possibly marking all of them with a distinct new color that you know doesn't exist in your dataset otherwise (0, 0, 0), for example - and marking it's label to be 'unknown'. This is slightly outside the scope of your original question - hence just a text description of it here.

Related

Binarize image data

I have 10 greyscale brain MRI scans from BrainWeb. They are stored as a 4d numpy array, brains, with shape (10, 181, 217, 181). Each of the 10 brains is made up of 181 slices along the z-plane (going through the top of the head to the neck) where each slice is 181 pixels by 217 pixels in the x (ear to ear) and y (eyes to back of head) planes respectively.
All of the brains are type dtype('float64'). The maximum pixel intensity across all brains is ~1328 and the minimum is ~0. For example, for the first brain, I calculate this by brains[0].max() giving 1328.338086605072 and brains[0].min() giving 0.0003886114541273855. Below is a plot of a slice of a brain[0]:
I want to binarize all these brain images by rescaling the pixel intensities from [0, 1328] to {0, 1}. Is my method correct?
I do this by first normalising the pixel intensities to [0, 1]:
normalized_brains = brains/1328
And then by using the binomial distribution to binarize each pixel:
binarized_brains = np.random.binomial(1, (normalized_brains))
The plotted result looks correct:
A 0 pixel intensity represents black (background) and 1 pixel intensity represents white (brain).
I experimented by implementing another method to normalise an image from this post but it gave me just a black image. This is because np.finfo(np.float64) is 1.7976931348623157e+308, so the normalization step
normalized_brains = brains/1.7976931348623157e+308
just returned an array of zeros which in the binarizition step also led to an array of zeros.
Am I binarising my images using a correct method?
Your method of converting the image to a binary image basically amounts to random dithering, which is a poor method of creating the illusion of grey values on a binary medium. Old-fashioned print is a binary medium, they have fine-tuned the methods to represent grey-value photographs in print over centuries. This process is called halftoning, and is shaped in part by properties of ink on paper, that we do not have to deal with in binary images.
So what methods have people come up with outside of print? Ordered dithering (mostly Bayer matrix), and error diffusion dithering. Read more about dithering on Wikipedia. I wrote a blog post showing how to implement all of these methods in MATLAB some years ago.
I would recommend you use error diffusion dithering for your particular application. Here is some code in MATLAB (taken from my blog post liked above) for the Floyd-Steinberg algorithm, I hope that you can translate this to Python:
img = imread('https://i.stack.imgur.com/d5E9i.png');
img = img(:,:,1);
out = double(img);
sz = size(out);
for ii=1:sz(1)
for jj=1:sz(2)
old = out(ii,jj);
%new = 255*(old >= 128); % Original Floyd-Steinberg
new = 255*(old >= 128+(rand-0.5)*100); % Simple improvement
out(ii,jj) = new;
err = new-old;
if jj<sz(2)
% right
out(ii ,jj+1) = out(ii ,jj+1)-err*(7/16);
end
if ii<sz(1)
if jj<sz(2)
% right-down
out(ii+1,jj+1) = out(ii+1,jj+1)-err*(1/16);
end
% down
out(ii+1,jj ) = out(ii+1,jj )-err*(5/16);
if jj>1
% left-down
out(ii+1,jj-1) = out(ii+1,jj-1)-err*(3/16);
end
end
end
end
imshow(out)
Resampling the image before applying the dithering greatly improves the results:
img = imresize(img,4);
% (repeat code above)
imshow(out)
NOTE that the above process expects the input to be in the range [0,255]. It is easy to adapt to a different range, say [0,1328] or [0,1], but it is also easy to scale your images to the [0,255] range.
Have you tried a threshold on the image?
This is a common way to binarize images, rather than trying to apply a random binomial distribution. You could try something like:
binarized_brains = (brains > threshold_value).astype(int)
which returns an array of 0s and 1s according to whether the image value was less than or greater than your chosen threshold value.
You will have to experiment with the threshold value to find the best one for your images, but it does not need to be normalized first.
If this doesn't work well, you can also experiment with the thresholding options available in the skimage filters package.
IT is easy in OpenCV. as mentioned a very common way is defining a threshold, But your result looks like you are allocating random values to your intensities instead of thresholding it.
import cv2
im = cv2.imread('brain.png', cv2.CV_LOAD_IMAGE_GRAYSCALE)
(th, brain_bw) = cv2.threshold(imy, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
th = (DEFINE HERE)
im_bin = cv2.threshold(im, th, 255, cv
cv2.imwrite('binBrain.png', brain_bw)
brain
binBrain

Labeling image regions

I have an image containing coloured regions (some of them using the same colour) and I would like that each region have a different colour.
The objective is to colour/label each region using a different colours/labels.
Sample image:
You can achieve this by looping over the unique values in your image, creating a mask of the objects with that value, and performing bwlabel for each such mask. This will give you unique labels for each connected component in that mask, and you can collect the labels from all the masks by adding the number of labels already found previously:
img = imread('i5WLA.png');
index = zeros(size(img));
for iGray = unique(img(:)).' %'
mask = (img == iGray);
L = bwlabel(mask, 4);
index(mask) = L(mask)+max(index(:));
end
subplot(2,1,1);
imshow(img, []);
title('Original');
subplot(2,1,2);
imshow(index, []);
title('Each region labeled uniquely');
And here's the plot this makes:
You can now see that each connected object has its own unique gray value. You can then create a color image from this new indexed image using either ind2rgb or label2rgb and selecting a colormap to use (here I'm using hsv):
rgbImage = ind2rgb(index, hsv(max(index(:))));
imshow(rgbImage);
% Or...
rgbImage = label2rgb(index, #hsv);
imshow(rgbImage);
Unless there's already a function floating around that does what you want, you can always write it yourself.
If I had to do this, I'd consider something like a union-find algorithm to group all equal-color adjacent/connected pixels into sets, then assign labels to those sets.
A naive (less efficient but doesn't require union-find) implementation using pseudo-code:
# assume pixels are stored in numpy array. Use your imagination to adjust as required.
put each pixel into its own set.
for pixel in pixels:
neighbors = adjacent_same_color_pixels(pixel)
find the sets that contain pixel, and the sets that contain the neighbors
join all those sets together, delete the original sets
now there's one set for each connected same-color shape.
assign labels as desired.

Python Pillow: how to overlay one binary image on top of another to produce a composite?

I am doing some image processing, and I need to check if a binary image is identical to another.
Processing speed isn't an issue, and the simple thing I thought to do was count the white pixels remaining after adding the inverse of image A to image B (these images are very nearly identical, but not quite--some sort of distance metric is the goal).
Note: take the logarithm to linearize the distance
However, in order to create the composite image, I need to include a "mask" that is the same size as the two images.
I am having trouble finding an example of creating the mask online and using it for the Image.composite function.
Here is my code:
compA = ImageOps.invert(imgA)
imgAB = Image.composite(compA,imgB,??? mask)
Right now, I have created a mask of all zeros--however, the composite image does not appear correctly (both A and B are exactly the same images; a mask of all zeros--or all ones for that matter--does not work).
mask = Image.fromarray(np.zeros(imgA.size,dtype=int),mode='L')
imgAB = Image.composite(compA,imgB,mask)
How do I just add these two binary images on top of eachother?
Clearly you're using numpy, so why not just work with numpy arrays and explicitly do whatever arithmetic you want to do in that domain—such as subtracting one image from the other:
arrayA = numpy.asarray( imgA, dtype=int )
arrayB = numpy.asarray( imgB, dtype=int )
arrayDelta = arrayA - arrayB
print( (arrayDelta !=0 ).sum() ) # print the number of non-identical pixels (why count them by hand?)
# NB: this number may be inflated by a factor of 3 if there are 3 identical channels R, G, B
imgDelta = Image.fromarray((numpy.sign(arrayDelta)*127+127).astype('uint8')) # display this image if you want to visualize where the differences are
You could do this even more simply, e.g.
print((numpy.asarray(imgA) != numpy.asarray(imgB)).sum())
but I thought casting to a signed integer type first and then subtracting would allow you to visualize more information (A white and B black -> white pixel in delta; A black and B white -> black pixel in delta)

Coloring only the inside of a shape

Lets say that you are given this image
and are given the instruction to programmatically color only the inside of it the appropriate color, but the program would have to not only work on this shape and other primitives but on any outlined shape, however complex it may be and shaded or not.
This is the problem I am trying to solve, but here's where I'm stuck, it seems like it should be simple to teach a computer to see black lines, and color inside them. But searching mostly turns up eigenface style recognition algorithms, which seems to me to be over fitting and far greater complexity than is needed for at least the basic form of this problem.
I would like to frame this as a supervised learning classifier problem, the purpose of which is to feed my model a complete image and it will output smaller numpy arrays consisting of pixels classsified as object or background. But in order to do that I would need to give it training data, which to me seems like I would need to hand label every pixel in my training set, which obviously defeats the purpose of the program.
Now that you have the background, here's my question, given this image, is there an efficient way to get two distinct arrays, each consisting of all adjacent pixels that do not contain any solid black (RGB(0,0,0)) pixels?
Which would make one set all pixels on the inside of the circle, and the other, all pixels on the outside of the circle
You can use scipy.ndimage.measurements.label to do all the heavy lifting for you:
import scipy.ndimage
import scipy.misc
data = scipy.misc.imread(...)
assert data.ndim == 2, "Image must be monochromatic"
# finds and number all disjoint white regions of the image
is_white = data > 128
labels, n = scipy.ndimage.measurements.label(is_white)
# get a set of all the region ids which are on the edge - we should not fill these
on_border = set(labels[:,0]) | set(labels[:,-1]) | set(labels[0,:]) | set(labels[-1,:])
for label in range(1, n+1): # label 0 is all the black pixels
if label not in on_border:
# turn every pixel with that label to black
data[labels == label] = 0
This will fill all closed shapes within the image, considering a shape cut by the edge of the image not to be closed

How to make a color progression out of a color palette

My goal with this algorithm I'm working on is to output a color progression out of some provided colors. By color progression I mean creating the "fade" effect between two colors (color A, color B) and store every color value ((R,G,B) tuple) in between.
For example, if what is provided is total black A = (0,0,0) and total white B = (255,255,255) the progression resultant would be:
P = ((0,0,0),(1,1,1),(2,2,2), .... ,(253,253,253),(254,254,254),(255,255,255)
So that we get white at first and it progressively turns into black. It is, of course, very easy with white and black (just increase RGB by one each step for 255 times). But what if I want to do this procedure with two arbitrary colors, like A = (180,69,1) and B = (233,153,0)??
IMPORTANT NOTE: If with hexadecimal (or any other kind of color notation) it would be easier to achieve, I could work with that too, just specify which type is (take into account that I'm working on PIL (Python Imaging Library), so if it's compatible with that I'm fine)
Obviously it has to be an as even as possible distribution, the progression must be homogeneous.
I need to figure out this algorithm so that I can use it in my Fractal Generator (Mandelbrot Set, google it if you want), so it is important for the progression to be as soft as possible, no hiccups.
Thanks in advance.
Convert your RGB coordinates to HSL or HSV and step through them, converting back to RGB along the way.
I would just interpolate the RGB values independently. See this thread.
My answer to the SO question titled Range values to pseudocolor might be helpful to you because it shows one way to generate a specific color gradient (the common name of what you referred to as a color progression).
Generally you can interpolate between any two colors in any colorspace by computing the difference, or delta value, between the components of each color, and then divide those by the number of intermediate steps desired to get a fractional delta amount per component to apply after each step.
Then, starting from the value of each of the first color's components, the corresponding fractional amount can be added to it over-and-over to determine each intermediate step color. Alternatively, the arithmetic error inherent in that approach can be reduced by instead adding (step_number/total_steps) * fractional_delta to the initial color component values of the starting color.
I believe this is what #Jim Clay is also saying in his answer. If you'd like some sample code, say so in a comment.

Categories