Related
I'm trying to obtain the RGB histogram of a zone on a picture. I've already isolated my zone by thresholding the picture (the background is bright, and my isolated zone is dark). I know how to make the color histogram of my entire picture, but not the RGB histogram of just my zone, by using the contour of my zone as a mask in calcHist OpenCV function.
What I actually do is :
#I threshlod my picture to obtain my objects of interest
threshold = threshold3(img, param['thresh_red_low'], param['thresh_red_high'], param['thresh_green_low'], param['thresh_green_high'], param['thresh_blue_low'])
#I find contours of my objects
contours = cv2.findContours(threshold , cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
#For each of my objects
for indexx, contour in enumerate(contours):
#If I directly try to put contour as a mask in calcHist, I got an error
#I convert the contour into a mask
mask = cv2.drawContours(image_color, contour, -1, (255, 255, 255), 2)
#I calculate histograms for BGR channel, on ten ranges, from 5 to 256
b_hist = cv2.calcHist([image_color],[0],mask,[10],[5,256])
g_hist = cv2.calcHist([image_color],[1],mask,[10],[5,256])
r_hist = cv2.calcHist([image_color],[2],mask,[10],[5,256])
#Then I save results into a csv
But I got too many values in each of histogram range. For example, my first zone has an area of 6371 px, and its histogram values are :
Number of red pixels per range : 388997,500656,148124,97374,198893,793015,894672,1232693,674721,105807
Number of green pixels per range :
123052,478714,349357,153624,117838,105738,84656,1205018,1356478,1064373
Number of blue pixels per range :
1590057,702532,547988,430238,320658,103876,15366,7629,1527,2645
Which is more like the entire picture histogram than the zone's. What do I don't understand about mask and contour in calcHist function ?
Sorry for such a late response but this might help somebody else, I hope.
By and large your code is correct only that you may need to add just a line or two and modify one line a bit.
#I threshlod my picture to obtain my objects of interest
threshold = threshold3(img, param['thresh_red_low'], param['thresh_red_high'], param['thresh_green_low'], param['thresh_green_high'], param['thresh_blue_low'])
#I find contours of my objects
contours = cv2.findContours(threshold , cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
#For each of my objects
for indexx, contour in enumerate(contours):
#If I directly try to put contour as a mask in calcHist, I got an error
#I convert the contour into a mask
w, h = img.shape
mask = np.zeros((h, w), dtype="uint8")
cv2.drawContours(mask, contours, indexx, 255, cv2.FILLED)
#I calculate histograms for BGR channel, on ten ranges, from 5 to 256
b_hist = cv2.calcHist([image_color],[0],mask,[10],[5,256])
g_hist = cv2.calcHist([image_color],[1],mask,[10],[5,256])
r_hist = cv2.calcHist([image_color],[2],mask,[10],[5,256])
#Then I save results into a csv
This solution assumes that you are interested in everything that's been found inside the contour.
Hey there I am using Opencv3.3 and Pyhton2.7 for recognizing a Maze in an image.
I have to find the outermost limit of the Maze in the image.
I tried closing the entrance and exit gaps of the maze and finding the outermost shape. I worked on this for closing the gaps but it is useless for my problem because I need these gaps to solve the maze.
This is the original image
I want to find outermost limit of the maze.
This is what I want
How can I extract outermost contour?
I would do this with numpy rather than OpenCV, but the two are compatible so you can mix and match anyway, or you can adapt the technique to OpenCV once you get the idea of how I am tackling it.
The strategy is to sum all the pixels across every row and make a single pixel wide image (shown on the right below) that is the sum of all the pixels in each row. I then find the biggest value in that column and divide by that to normalise everything to the range 0..100. Now any pixel that is less than 30 in that single pixel wide image means that the corresponding row had less than 30% of white pixels in the original image - i.e. it was largely black.
Then I make the same summation of all the columns to produce the column sums - shown across the bottom of the image below:
I think some folks refer to this technique as a "projection" if you want to Google it.
So, the code looks like this:
#!/usr/local/bin/python3
import numpy as np
from PIL import Image
# Load image - you can use OpenCV "imread()" just the same and convert to grayscale
im = np.array(Image.open('maze.jpg').convert('L'))
# Get height and width
h,w = im.shape[0:2]
# Make a single pixel wide column, same height as image to store row sums in
rowsums=np.empty((h))
# Sum all pixels in each row
np.sum(im,axis=1,out=rowsums)
# Normalize to range 0..100, if rowsum[i] < 30 that means fewer than 30% of the pixels in row i are white
rowsums /= np.max(rowsums)/100
# Find first and last row that is largely black
first = last = -1
for r in range(h):
if first < 0 and rowsums[r] < 30:
first = r
if rowsums[r] < 30:
last = r
print(first,last)
# Make a single pixel tall row, same width as image to store col sums in
colsums=np.empty((w))
# Sum all pixels in each col
np.sum(im,axis=0,out=colsums)
# Normalize to range 0..100, if colsum[i] < 30 that means fewer than 30% of the pixels in col i are white
colsums /= np.max(colsums)/100
# Find first and last col that is largely black
first = last = -1
for c in range(w):
if first < 0 and colsums[c] < 30:
first = c
if colsums[c] < 30:
last = c
print(first,last)
That outputs:
62 890
36 1509
So the top row of the maze is row 62, and the bottom one is row 890. The left column of the maze is column 36 and the rightmost column is col 1509.
If I draw on an 80% transparent red rectangle to match those locations, I get:
I am attempting to threshold a wave so that the white background appears black and the wave itself which was originally black is white, however it only seems to return an entirely black image. What am I doing wrong?
import cv2
src = cv2.imread("C:\\Users\\ksatt\\Desktop\\SoundByte\\blackwaveblackaxis (1).PNG",0)
maxValue = 255
thresh= 53
if not src is None:
th, dst = cv2.threshold(src, thresh, maxValue, cv2.THRESH_BINARY_INV)
cv2.imshow("blackwave.PNG", dst)
cv2.imwrite("blackwave.PNG", dst)
cv2.waitKey(0)
else:
print 'Image could not be read'
Your threshold is too low, and the dark paper is going to pick up values that you don't want anyways. Basically, the contrast of the image is too low.
One easy solution is to subtract out the background. The simple way to do this is to dilate() your grayscale image, which will expand the white area and overtake the black lines. Then you can apply a small GaussianBlur() to that dilated image, and this will give you a "background" image that you can subtract from your original image to get a clear view of the lines. From there you'll have a much better image to threshold(), and you can even use OTSU thresholding to automatically set the threshold level for you.
import cv2
import numpy as np
# read image
src = cv2.imread('wave.png',0)
# create background image
bg = cv2.dilate(src, np.ones((5,5), dtype=np.uint8))
bg = cv2.GaussianBlur(bg, (5,5), 1)
# subtract out background from source
src_no_bg = 255 - cv2.absdiff(src, bg)
# threshold
maxValue = 255
thresh = 240
retval, dst = cv2.threshold(src_no_bg, thresh, maxValue, cv2.THRESH_BINARY_INV)
# automatic / OTSU threshold
retval, dst = cv2.threshold(src_no_bg, 0, maxValue, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
You can see that manual thresholding gives the same results as OTSU, but you don't have to play around with the values for OTSU, it'll find them for you. This isn't always the best way to go but it can be quick sometimes. Check out this tutorial for more on different thresholding operations.
if you take a look at http://docs.opencv.org/2.4/modules/imgproc/doc/miscellaneous_transformations.html#threshold it will tell you what each parameter does of the function.
Also here is a good tutorial:
http://docs.opencv.org/trunk/d7/d4d/tutorial_py_thresholding.html
Python: cv.Threshold(src, dst, threshold, maxValue, thresholdType) →
None
Is the prototype which gets further explenation in the mentioned API.
So simply change your code to:
cv2.threshold(src,RESULT, thresh, maxValue, cv2.THRESH_BINARY_INV)
cv2.imshow("blackwave.PNG", RESULT)
Could you post a picture of the wave? Have you tried using standard python? Something like this should work:
import numpy as np
import matplotlib.pyplot as plt
maxValue = 255
thresh= 53
A = np.load('file.png')
# For each pixel, see if it's above/below the threshold
for i in range(A.shape[0]): # Loop along the X direction
for j in range(A.shape[1]): # Loop along the Y direction
# Set to black the background
if A[i,j] > thresh:
A[i,j] = 0
if A[i,j] == 0:
A[i,j] = 255
Or something similar.
i have a picture from a laser line and i would like to extract that line out of the image.
As the laser line is red, i take the red channel of the image and then searching for the highest intensity in every row:
The problem now is, that there are also some points which doesnt belong to the laser line (if you zoom into the second picture, you can see these points).
Does anyone have an idea for the next steps (to remove the single points and also to extract the lines)?
That was another approach to detect the line:
First i blurred out that "black-white" line with a kernel, then i thinned(skeleton) that blurred line to a thin line, then i applied an OpenCV function to detect the line.. the result is in the below image:
NEW:
Now i have another harder situation.
I have to extract a green laser light.
The problem here is that the colour range of the laser line is wider and changing.
On some parts of the laser line the pixel just have high green component, while on other parts the pixel have high blue component as well.
Getting the highest value in every row will always output a value, instead of ignoring when the value isn't high enough. Consider using a threshold too, so that you can discard ones that aren't high enough.
However, that's not a very efficient way to do this at all. A much better and easier solution would be to use the OpenCV function inRange(); define a lower and upper bound for the red color in all three channels, and this will return a binary image with white pixels where the image intensity is within that BGR range.
This is in python but it does the job, should be easy to see how to use the function:
import cv2
import numpy as np
img = cv2.imread('image.png')
lowerb = np.array([0, 0, 120])
upperb = np.array([100, 100, 255])
red_line = cv2.inRange(img, lowerb, upperb)
cv2.imshow('red', red_line)
cv2.waitKey(0)
This produces the output:
This could be further processed by finding contours or other methods to turn the points into a nice curve.
I'm really sorry for the short answer without any code, but I suggest you take contours and process them.
I dont know exact what you need, so here are two approaches for you:
just collect as much as possible contours on single line (use centers and try find straight line with smallest mean)
as first way, but trying heuristically combine separated lines.... it's much harder, but this may give you almost full laser line from image.
--
Some example for yours picture:
import cv2
import numpy as np
import math
img = cv2.imread('image.png')
hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
# filtering red area of hue
redHueArea = 15
redRange = ((hsv[:, :, 0] + 360 + redHueArea) % 360)
hsv[np.where((2 * redHueArea) > redRange)] = [0, 0, 0]
# filtering by saturation
hsv[np.where(hsv[:, :, 1] < 95)] = [0, 0, 0]
# convert to rgb
rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
# select only red grayscaled channel with low threshold
gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
gray = cv2.threshold(gray, 15, 255, cv2.THRESH_BINARY)[1]
# contours processing
(_, contours, _) = cv2.findContours(gray.copy(), cv2.RETR_LIST, 1)
for c in contours:
area = cv2.contourArea(c)
if area < 8: continue
epsilon = 0.1 * cv2.arcLength(c, True) # tricky smoothing to a single line
approx = cv2.approxPolyDP(c, epsilon, True)
cv2.drawContours(img, [approx], -1, [255, 255, 255], -1)
cv2.imshow('result', img)
cv2.waitKey(0)
In your case it's work perfectly, but, as i already said, you will need to do much more work with contours.
I have stitched two images together using OpenCV functions and C++. Now I am facing a problem that the final image contains a large black part.
The final image should be a rectangle containing the effective part.
My image is the following:
How can I remove the black section?
mevatron's answer is one way where amount of black region is minimised while retaining full image.
Another option is removing complete black region where you also loose some part of image, but result will be a neat looking rectangular image. Below is the Python code.
Here, you find three main corners of the image as below:
I have marked those values. (1,x2), (x1,1), (x3,y3). It is based on the assumption that your image starts from (1,1).
Code :
First steps are same as mevatron's. Blur the image to remove noise, threshold the image, then find contours.
import cv2
import numpy as np
img = cv2.imread('office.jpg')
img = cv2.resize(img,(800,400))
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray = cv2.medianBlur(gray,3)
ret,thresh = cv2.threshold(gray,1,255,0)
contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
Now find the biggest contour which is your image. It is to avoid noise in case if any (Most probably there won't be any). Or you can use mevatron's method.
max_area = -1
best_cnt = None
for cnt in contours:
area = cv2.contourArea(cnt)
if area > max_area:
max_area = area
best_cnt = cnt
Now approximate the contour to remove unnecessary points in contour values found, but it preserve all corner values.
approx = cv2.approxPolyDP(best_cnt,0.01*cv2.arcLength(best_cnt,True),True)
Now we find the corners.
First, we find (x3,y3). It is farthest point. So x3*y3 will be very large. So we find products of all pair of points and select the pair with maximum product.
far = approx[np.product(approx,2).argmax()][0]
Next (1,x2). It is the point where first element is one,then second element is maximum.
ymax = approx[approx[:,:,0]==1].max()
Next (x1,1). It is the point where second element is 1, then first element is maximum.
xmax = approx[approx[:,:,1]==1].max()
Now we find the minimum values in (far.x,xmax) and (far.y, ymax)
x = min(far[0],xmax)
y = min(far[1],ymax)
If you draw a rectangle with (1,1) and (x,y), you get result as below:
So you crop the image to correct rectangular area.
img2 = img[:y,:x].copy()
Below is the result:
See, the problem is that you lose some parts of the stitched image.
You can do this with threshold, findContours, and boundingRect.
So, here is a quick script doing this with the python interface.
stitched = cv2.imread('stitched.jpg', 0)
(_, mask) = cv2.threshold(stitched, 1.0, 255.0, cv2.THRESH_BINARY);
# findContours destroys input
temp = mask.copy()
(contours, _) = cv2.findContours(temp, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# sort contours by largest first (if there are more than one)
contours = sorted(contours, key=lambda contour:len(contour), reverse=True)
roi = cv2.boundingRect(contours[0])
# use the roi to select into the original 'stitched' image
stitched[roi[1]:roi[3], roi[0]:roi[2]]
Ends up looking like this:
NOTE : Sorting may not be necessary with raw imagery, but using the compressed image caused some compression artifacts to show up when using a low threshold, so that is why I post-processed with sorting.
Hope that helps!
You can use active contours (balloons/snakes) for selecting the black region accurately. A demonstration can be found here. Active contours are available in OpenCV, check cvSnakeImage.