Identifying color sequence in opencv - opencv

I have set of images with four possible color R, G, B and Y. In front of camera I have sequence of four images with any possible combination of color. For ex. R,R,G,B or R,G,B,Y etc. In order to find the correct sequence which algorithm or approach is best?
I have added an example image.
The code should return correct sequence as RGBG.

As I mentioned before, convert the image to HSV plane. HSV plane is more better to choose a specific color. (Code is in Python)
import numpy as np
import cv2
img = cv2.imread('sofqn.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
hsv = cv2.cvtColor(gray,cv2.COLOR_BGR2HSV)
Then binarize the image. You can use threshold() function. But I don't know how your brightness would be. So I for edge detection with Canny(). And find contours in it.
edges = cv2.Canny(img,50,150)
contours,hierarchy = cv2.findContours(edges,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
Then for each contour (you can consider contour as an object in your image for now), find its area. If it is small, it is noise, avoid it. Else, we find a bounding rectangle for it, which gives us its topleft corners(x,y), width(w),height(h). From that, we find center point of the square. Check its color in HSV image, and check if it is R,G,B,Y. We put them, ie centroids (cx,cy) and color in a list, (or array). Finally we sort them as per x coordinate, so that first row corresponds to first square and so on.
res = []
for cnt in contours:
if cv2.contourArea(cnt) > 100:
x,y,w,h = cv2.boundingRect(cnt)
cx,cy = x+w/2, y+h/2
color = hsv[cy,cx,0]
if (color < 10 or color > 170):
res.append([cx,cy,'R'])
elif(50 < color < 70):
res.append([cx,cy,'G'])
elif(20 < color <40):
res.append([cx,cy,'Y'])
elif(110 < color < 130):
res.append([cx,cy,'B'])
res = sorted(res,key = lambda res : res[0])
colors = [x[2] for x in res]
print colors
This gives me the result : ['R', 'G', 'B', 'G']

Related

How to add transformation of white lines on a picture in python/pytorch?

I'm trying to add faint white diagonal lines (similar to these lines except that they're diagonal) into images for a machine learning task. Does anyone know whether these transformations have a name and how I can replicate them on images (in python/pytorch preferably)?
Check out this fragment of code:
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
def add_lines(image, line_width = 5, line_intensity = 0.8, row_effect = 0.45):
s_shape = (image.height, image.width)
if row_effect == "aspect":
row_effect = image.width/image.height
lines = Image.fromarray(np.full(s_shape, 255, dtype=np.uint8), "L").convert("RGBA")
alpha = np.zeros(s_shape)
line_fun = lambda x : int(line_intensity*255/2*(np.sin(2*np.pi/line_width*x)+1))
for col_index in range(alpha.shape[1]):
for row_index in range(alpha.shape[0]):
alpha[row_index, col_index] = line_fun(col_index + row_effect*row_index)
alpha = Image.fromarray(np.uint8(alpha), "L")
new_image = image.copy().convert("RGBA")
new_image.paste(lines, (0,0), alpha)
return new_image
source = Image.open("source.jpg")
source_lines = add_lines(source, line_width=5, line_intensity=0.4, row_effect="aspect")
_, axs = plt.subplots(1, 2)
axs[0].imshow(source)
axs[1].imshow(source_lines)
The most important is of course the add_lines function.
To solve Your problem we proceed as follows:
Create a fully white grayscale image with the shape of the source image,
create an array of the same size as the source, but with all zeros,
define a sine function, rescaled so it is in range [0, line_intensity*255], and with period of line_width,
for each column in the alpha array fill it with the value of the sine function with the column index as a parameter, and some percentage of the row index as well, this percentage defines how much "diagonal" will the lines be. If You need a 45 degree angle of the lines, just set row_effect to 1, and if You need it to go exactly diagonally through the image, set it to the aspect ratio of the image.
convert both the source and the lines to RGBA, and the alpha mask to grayscale,
paste the lines image to the source, using alpha as the mask.
This relatively simple code produces the following result:

find contours finds too many contours on simulated image

I want to find the contours of a binary image of segmented rocks. There are some problems with the findContours function from opencv.
The contour size is around 1000 while the contours from the binary image could be around 30-50.
When I draw ALL the contours, they seem to be a decent representation of the black boundaries from the binary image. But When I draw only one contour of some random index, it shows a small contour.
Images are given below :
Binary Image
Contours of all the index
Contour of a random contour index. The small green contour
I would like to have just the exact number of contours as in the binary image.
Code :
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(input_image, contours,hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);
for( int i = 0; i < (int)contours.size(); i++)
{
cv::drawContours(input_rgb_image, contours, 512 , cv::Scalar(0,255,0), 1, 8, hierarchy,1);
}
There are two problems with your code. You will get better results if you invert and blur the image. These are my results after applying those two operations before finding the contours:
The OpenCV findContours() function finds dark contours on the light background. If you want to find the white spaces, which are the rocks, you need to invert the binary image first. You can invert a binary image like this invertedImage = 255 - binaryImage. Blurring also helps because it connects pixels that should be connected but aren't because of the low resolution. Blurring is done with the code blurredImage = cv2.blur(img, (2,2)). This is the inverted blurred image:
This is the code that I used:
import cv2
import random
# Read image
gray = 255-cv2.imread('/home/stephen/Desktop/image.png', 0)
gray = cv2.blur(gray, (2,2))
# Find contours in image
contours, _ = cv2.findContours(gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours))
img = cv2.imread('/home/stephen/Desktop/image.png')
for cnt in contours:
color = random.randint(0,255),random.randint(0,255),random.randint(0,255)
img = cv2.drawContours(img, [cnt], 0, color, cv2.FILLED)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
I would try a couple of things:
bilateral filter instead of blur. It smoothes things in a way similar
to blur but also tries to preserve boundaries, which is good for segmentation. Downsides - it's computationally expensive but you may
find "your" params that play well for free
blur + meanshift segmentation before the watershed. Blur will act just
like expected and meanshift will average and join contours with
similar colors and as such make the number of contours smaller.
Depending on params, meanshift is also expensive. Just play with
it.
More advanced thing is contours analysis afterward. You could unite some of the neighbors based on:
the similarity of the histogram on some of hsv channels;
contours properties, such as roundness. If roundness of two united
neighbors is better than the roundness of any of them then they can be united. Something like this.
Roundness calculating:
float calcRoundness(std::vector<cv::Point> &contour, double area)
{
float p = cv::arcLength(contour, true);
if (p == 0)
return 0;
float k = (4 * M_PI * area) / pow(p, 2);
/* 1 is circle, 0.75 - squared area, etc. */
return k;
}

How can I detect uniform color rectangles in an image using OpenCV?

I would like to use OpenCV to detect which rectangles in an image have a majority of pixels close to a given color.
Here's an example of an image I would like to process using this to identify rectangular regions that contain mostly gray pixels (possibly roads):
More precisely, given:
dimensions h x w (height and weight of candidate rectangles)
a distance function dist for colors (for example, the norm of the vector difference between the color vector, which could be RGB or any other representation)
a color vector C
a maximum distance d for colors to be from C
a minimum percentage rate r of pixels in a given rectangle to be within distance d from C for the rectangle to be of interest,
return a mask M in which each pixel P is 1 if the rectangle of size h x w left-cornered by P contains at least r % of its pixels within distance d from C when measured with dist.
In pseudo-code, pixel P in the mask is 1 if and only if:
def rectangle_left_cornered_at_P_is_of_interest(P):
n_pixels_near_C = size([P' for P' in rectangle(P, P + (h,w)) if dist(P',C) < d])
return n_pixels_near_C / (h * w) > r
I imagine there may already exist a filter/kernel that does just that (or can be used to do that) in OpenCV, but I am still learning about it and could not identify one by looking at the documentation. Is there such a thing?
You can use HSV for this . you may have to play with the values a bit for the mask but it will get the job done.
img = cv2.imread(img)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_gray = np.array([0, 5, 50], np.uint8)
upper_gray = np.array([350, 50, 255], np.uint8)
mask = cv2.inRange(hsv, lower_gray, upper_gray)
img_res = cv2.bitwise_and(img, img, mask = mask)
cv2.imwrite('gray.png',img_res)
You should also refer to this post. Its a good post on the use of HSV.
Basicly all you will need for this job will be :
HSV masks,
Otsu thresholding , blurs and may be erosion and dilation.
Use them in some combition that fits your requirement best.

How to get the area of the contours?

I have a picture like this:
And then I transform it into binary image and use canny to detect edge of the picture:
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
edge = Image.fromarray(edges)
And then I get the result as:
I want to get the area of 2 like this:
My solution is to use HoughLines to find lines in the picture and calculate the area of triangle formed by lines. However, this way is not precise because the closed area is not a standard triangle. How to get the area of region 2?
A simple approach using floodFill and countNonZero could be the following code snippet. My standard quote on contourArea from the help:
The function computes a contour area. Similarly to moments, the area is computed using the Green formula. Thus, the returned area and the number of non-zero pixels, if you draw the contour using drawContours or fillPoly, can be different. Also, the function will most certainly give a wrong results for contours with self-intersections.
Code:
import cv2
import numpy as np
# Input image
img = cv2.imread('images/YMMEE.jpg', cv2.IMREAD_GRAYSCALE)
# Needed due to JPG artifacts
_, temp = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY)
# Dilate to better detect contours
temp = cv2.dilate(temp, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)))
# Find largest contour
cnts, _ = cv2.findContours(temp, cv2.RETR_EXTERNAL , cv2.CHAIN_APPROX_NONE)
largestCnt = []
for cnt in cnts:
if (len(cnt) > len(largestCnt)):
largestCnt = cnt
# Determine center of area of largest contour
M = cv2.moments(largestCnt)
x = int(M["m10"] / M["m00"])
y = int(M["m01"] / M["m00"])
# Initiale mask for flood filling
width, height = temp.shape
mask = img2 = np.ones((width + 2, height + 2), np.uint8) * 255
mask[1:width, 1:height] = 0
# Generate intermediate image, draw largest contour, flood filled
temp = np.zeros(temp.shape, np.uint8)
temp = cv2.drawContours(temp, largestCnt, -1, 255, cv2.FILLED)
_, temp, mask, _ = cv2.floodFill(temp, mask, (x, y), 255)
temp = cv2.morphologyEx(temp, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)))
# Count pixels in desired region
area = cv2.countNonZero(temp)
# Put result on original image
img = cv2.putText(img, str(area), (x, y), cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, 255)
cv2.imshow('Input', img)
cv2.imshow('Temp image', temp)
cv2.waitKey(0)
Temporary image:
Result image:
Caveat: findContours has some problems one the right side, where the line is very close to the bottom image border, resulting in possibly omitting some pixels.
Disclaimer: I'm new to Python in general, and specially to the Python API of OpenCV (C++ for the win). Comments, improvements, highlighting Python no-gos are highly welcome!
There is a very simple way to find this area, if you take some assumptions that are met in the example image:
The area to be found is bounded on top by a line
Any additional lines in the image are above the line of interest
There are no discontinuities in the line
In this case, the area of the region of interest is given by the sum of the lengths from the bottom of the image to the first set pixel. We can compute this with:
import numpy as np
import matplotlib.pyplot as pp
img = pp.imread('/home/cris/tmp/YMMEE.jpg')
img = np.flip(img, axis=0)
pos = np.argmax(img, axis=0)
area = np.sum(pos)
print('Area = %d\n'%area)
This prints Area = 22040.
np.argmax finds the first set pixel on each column of the image, returning the index. By first using np.flip, we flip this axis so that the first pixel is actually the one on the bottom. The index corresponds to the number of pixels between the bottom of the image and the line (not including the set pixel).
Thus, we're computing the area under the line. If you need to include the line itself in the area, add pos.shape[0] to the area (i.e. the number of columns).

how to superimpose two images?

I have a visualization output of gabor filter with 12 different orientations.I want to superimpose the vizualization image on my image of retina for vessel extraction.How do i do it?I have tried the below method.is there any other method to perform superimposition of images in matlab.
here is my code
I = getimage();
I=I(:,:,2);
lambda = 8;
theta = 0;
psi = [0 pi/2];
gamma = 0.5;
bw = 1;
N = 2;
img_in = im2double(I);
%img_in(:,:,2:3) = []; % discard redundant channels, it's gray anyway
img_out = zeros(size(img_in,1), size(img_in,2), N);
for n=1:N
gb = gabor_fn(bw,gamma,psi(1),lambda,theta)...
+ 1i * gabor_fn(bw,gamma,psi(2),lambda,theta);
% gb is the n-th gabor filter
img_out(:,:,n) = imfilter(img_in, gb, 'symmetric');
% filter output to the n-th channel
%theta = theta + 2*pi/N
%figure;
%imshow(img_out(:,:,n));
imshow(img_in); hold on;
h = imagesc(img_out(:,:,n)); % here i am getting error saying CDATA must be size[M*N]
set( h, 'AlphaData', .5 ); % .5 transparency
figure;
imshow(h);
theta = 15 * n; % next orientation
end
this is my original image
this is my visualized image got by gabor filter using orientation
this is the kind/type of image i have to get with respect to visualisation .i.e i have to impose visualized image on my original image and i have to get this type of image
With the information you have provided, my understanding is you want the third/final image to be an overlay on top of the first/initial image. I do things like this when using segmentation to detect hemorrhaging in MRI images of the brain.
First, let's set up some defintions:
I_src = source/original image
I_out = output/final image
Now, make a copy of I_src and make it a color image rather than grayscale.
I_hybrid = I_src
colorIm = gray2rgb(I_src)
Let's assume both I_src and I_out are the same visual dimensions (ie: width, height), and that I_out is strictly black-and-white (ie: monochrome). Now, we can use I_out as a mask template for alpha channel adjustments in the resulting image. This is where it gets fun.
BLACK=0;
WHITE=1;
[length width] = size(I_out);
for i = 1:1:length
for j = 1:1:width
if (I_out(i,j) == WHITE)
I_hybrid(i,j) = I_hybrid(i,j) + [0.25 0 0]a;
end
end
This will result in you getting your original image with the blood vessels in the eye being slightly brighter and tinted red. You now have a beautiful composite of your original image with the desired features highlighted, but not overwritten (ie: you can undo the highlighting by subtracting the original color vector).
I will include an example of what the output would look like, but it's noisy because I had to create it in GIMP as I don't have Matlab installed right now. The results will be similar, but yours would be much cleaner and prettier.
Please let me know how this goes.
References
"Converting Images from Grayscale to Color" http://blogs.mathworks.com/pick/2012/11/25/converting-images-from-grayscale-to-color/

Resources