I am trying to increase the region of interest of an image using the below algorithm.
First, the set of pixels of the exterior border of the ROI is de termined, i.e., pixels that are outside the ROI and are neighbors (using four-neighborhood) to pixels inside it. Then, each pixel value of this set is replaced with the mean value of its neighbors (this time using eight-neighborhood) inside the ROI. Finally, the ROI is expanded by inclusion of this altered set of pixels. This process is repeated and can be seen as artificially increasing the ROI.
The pseudocode is below -
while there are border pixels:
border_pixels = []
# find the border pixels
for each pixel p=(i, j) in image
if p is not in ROI and ((i+1, j) in ROI or (i-1, j) in ROI or (i, j+1) in ROI or (i, j-1) in ROI) or (i-1,j-1) in ROI or (i+1,j+1) in ROI):
add p to border_pixels
# calculate the averages
for each pixel p in border_pixels:
color_sum = 0
count = 0
for each pixel n in 8-neighborhood of p:
if n in ROI:
color_sum += color(n)
count += 1
color(p) = color_sum / count
# update the ROI
for each pixel p=(i, j) in border_pixels:
set p to be in ROI
Below is my code
img = io.imread(path_dir)
newimg = np.zeros((584, 565,3))
mask = img == 0
while(1):
border_pixels = []
for i in range(img.shape[0]):
for j in range(img.shape[1]):
for k in range(0,3):
if(i+1<=583 and j+1<=564 and i-1>=0 and j-1>=0):
if ((mask[i][j][k]) and ((mask[i+1][j][k]== False) or (mask[i-1][j][k]==False) or (mask[i][j+1][k]==False) or (mask[i][j-1][k]==False) or (mask[i-1][j-1][k] == False) or(mask[i+1][j+1][k]==False))):
border_pixels.append([i,j,k])
if len(border_pixels) == 0:
break
for (each_i,each_j,each_k) in border_pixels:
color_sum = 0
count = 0
eight_neighbourhood = [[each_i-1,each_j],[each_i+1,each_j],[each_i,each_j-1],[each_i,each_j+1],[each_i-1,each_j-1],[each_i-1,each_j+1],[each_i+1,each_j-1],[each_i+1,each_j+1]]
for pix_i,pix_j in eight_neighbourhood:
if (mask[pix_i][pix_j][each_k] == False):
color_sum+=img[pix_i,pix_j,each_k]
count+=1
print(color_sum//count)
img[each_i][each_j][each_k]=(color_sum//count)
for (i,j,k) in border_pixels:
mask[i,j,k] = False
border_pixels.remove([i,j,k])
io.imsave("tryout6.png",img)
But it is not doing any change in the image.I am getting the same image as before
so I tried plotting the border pixel on a black image of the same dimension for the first iteration and I am getting the below result-
I really don't have any idea where I am doing wrong here.
Here's a solution that I think works as you have requested (although I agree with #Peter Boone that it will take a while). My implementation has a triple loop, but maybe someone else can make it faster!
First, read in the image. With my method, the pixel values are floats between 0 and 1 (rather than integers between 0 and 255).
import urllib
import matplotlib.pyplot as plt
import numpy as np
from skimage.morphology import binary_dilation, binary_erosion, disk
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu
# create a file-like object from the url
f = urllib.request.urlopen("https://i.stack.imgur.com/JXxJM.png")
# read the image file in a numpy array
# note that all pixel values are between 0 and 1 in this image
a = plt.imread(f)
Second, add some padding around the edges, and threshold the image. I used Otsu's method, but #Peter Boone's answer works well, too.
# add black padding around image 100 px wide
a = np.pad(a, ((100,100), (100,100), (0,0)), mode = "constant")
# convert to greyscale and perform Otsu's thresholding
grayscale = rgb2gray(a)
global_thresh = threshold_otsu(grayscale)
binary_global1 = grayscale > global_thresh
# define number of pixels to expand the image
num_px_to_expand = 50
The image, binary_global1 is a mask that looks like this:
Since the image is three channels (RGB), I process the channels separately. I noticed that I needed to erode the image by ~5 px because the outside of the image has some unusual colors and patterns.
# process each channel (RGB) separately
for channel in range(a.shape[2]):
# select a single channel
one_channel = a[:, :, channel]
# reset binary_global for the each channel
binary_global = binary_global1.copy()
# erode by 5 px to get rid of unusual edges from original image
binary_global = binary_erosion(binary_global, disk(5))
# turn everything less than the threshold to 0
one_channel = one_channel * binary_global
# update pixels one at a time
for jj in range(num_px_to_expand):
# get 1 px ring of to update
px_to_update = np.logical_xor(binary_dilation(binary_global, disk(1)),
binary_global)
# update those pixels with the average of their neighborhood
x, y = np.where(px_to_update == 1)
for x, y in zip(x,y):
# make 3 x 3 px slices
slices = np.s_[(x-1):(x+2), (y-1):(y+2)]
# update a single pixel
one_channel[x, y] = (np.sum(one_channel[slices]*
binary_global[slices]) /
np.sum(binary_global[slices]))
# update original image
a[:,:, channel] = one_channel
# increase binary_global by 1 px dilation
binary_global = binary_dilation(binary_global, disk(1))
When I plot the output, I get something like this:
# plot image
plt.figure(figsize=[10,10])
plt.imshow(a)
This is an interesting idea. You're going to want to use masks and some form of mean ranks to accomplish this. Going pixel by pixel will take you a while, instead you want to use different convolution filters.
If you do something like this:
image = io.imread("roi.jpg")
mask = image[:,:,0] < 30
just_inside = binary_dilation(mask) ^ mask
image[~just_inside] = [0,0,0]
you will have a mask representing just the pixels inside of the ROI. I also set the pixels not in that area to 0,0,0.
Then you can get the pixels just outside of the roi:
just_outside = binary_erosion(mask) ^ mask
Then get the mean bilateral of each channel:
mean_blue = mean_bilateral(image[:,:,0], selem=square(3), s0=1, s1=255)
#etc...
This isn't exactly correct, but I think it should put you in the right direction. I would check out image.sc if you have more general questions about image processing. Let me know if you need more help as this was more general direction than working code.
Related
How can I detect laser line using 2 images, first with laser turned off and second with turned on and then calculate its center?
These are my images:
img1.jpg
img2.jpg
This is my code:
import cv2
import time
img1 = cv2.imread("img1.jpg")
img2 = cv2.imread("img2.jpg")
img_org = img1
img1 = img1[:,:,2]
img2 = img2[:,:,2]
diff = cv2.absdiff(img1, img2)
diff = cv2.medianBlur(diff,5)
ret, diff = cv2.threshold(diff ,0 ,255 ,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imwrite("output1.png", diff)
count = 0
height, width = diff.shape[:2]
start = time.time() # time start
for y in range(height):
for x in range(width):
if diff[y,x] == 255:
count += 1
elif not count == 0:
img_org[y, round(x - count/2)] = [0, 255, 0]
count = 0
end = time.time() # time stop
print(end - start)
cv2.imwrite("output2.png", img_org)
cv2.waitKey(0)
This code takes red channel from both images, compare them to detect difference, then blur and treshold the difference image. This doesnt work good enought because on the top is some white that shouldn't be there. output1.png (diff)
For detecting center of thresholded line I have tried looping through every row and pixel of the threshold image, counting white pixels. It works correcly but because of slow python loops and arrays calculating one 4032x2268 thresholded image takes about 16 seconds. For testing my code is setting laser line center to green pixels on output2.png. output2.png (img_org)
How can I make laser detection more accurate and center of line calculation way faster?
I'm fairly new to opencv.
difference
gaussian blur to suppress noise, and smooth over saturated sections
np.argmax to find maximum for each row
I would also recommend
some more reduction in exposure
PNG instead of JPEG for real processing. JPEG saves space, okay for viewing on the web.
Gamma curves don't necessarily matter here. Just make sure the environment is darker than the laser. Exact calculation depends on what color space it is exactly, and the 2.2 exponent is a good approximation of the actual curve
im0 = cv.imread("background.jpeg")
im1 = cv.imread("foreground.jpeg")
(height, width) = im0.shape[:2]
# gamma stuff, make values linear
#im0 = (im0 / np.float32(255)) ** 2.2
#im1 = (im1 / np.float32(255)) ** 2.2
diff = cv.absdiff(im1, im0)
diff = cv.GaussianBlur(diff, ksize=None, sigmaX=3.0)
plane = diff[:,:,2] # red
indices = np.argmax(plane, axis=1) # horizontally, for each row
out = diff.copy() # "drawing" 3 pixels thick
out[np.arange(height), indices-1] = (0,255,0)
out[np.arange(height), indices ] = (0,255,0)
out[np.arange(height), indices+1] = (0,255,0)
cv.imwrite("out.jpeg", out)
I want to make an affine transformation and afterwards use nearest neighbor interpolation while keeping the same dimensions for input and output images. I use for example the scaling transformation T= [[2,0,0],[0,2,0],[0,0,1]]. Any idea how can I fill the black pixels with nearest neighbor ? I tryied giving them the min value of neighbors' intensities. For ex. if a pixel has neighbors [55,22,44,11,22,55,23,231], I give it the value of min intensity: 11. But the result is not anything clear..
import numpy as np
from matplotlib import pyplot as plt
#Importing the original image and init the output image
img = plt.imread('/home/left/Desktop/computerVision/SET1/brain0030slice150_101x101.png',0)
outImg = np.zeros_like(img)
# Dimensions of the input image and output image (the same dimensions)
(width , height) = (img.shape[0], img.shape[1])
# Initialize the transformation matrix
T = np.array([[2,0,0], [0,2,0], [0,0,1]])
# Make an array with input image (x,y) coordinations and add [0 0 ... 1] row
coords = np.indices((width, height), 'uint8').reshape(2, -1)
coords = np.vstack((coords, np.zeros(coords.shape[1], 'uint8')))
output = T # coords
# Arrays of x and y coordinations of the output image within the image dimensions
x_array, y_array = output[0] ,output[1]
indices = np.where((x_array >= 0) & (x_array < width) & (y_array >= 0) & (y_array < height))
# Final coordinations of the output image
fx, fy = x_array[indices], y_array[indices]
# Final output image after the affine transformation
outImg[fx, fy] = img[fx, fy]
The input image is:
The output image after scaling is:
well you could simply use the opencv resize function
import cv2
new_image = cv2.resize(image, new_dim, interpolation=cv.INTER_AREA)
it'll do the resize and fill in the empty pixels in one go
more on cv2.resize
If you need to do it manually, then you could simply detect dark pixels in resized image and change their value to mean of 4 neighbour pixels (for example - it depends on your required alghoritm)
See: nereast neighbour, bilinear, bicubic, etc.
I want to count the pixels of color intensity of [150,150,150] in an image and I have determined the shape of the image and made a loop to scan the image pixel by pixel but I have faced this error and I don't know why it appeared.
But I got the following error:
File "D:/My work/MASTERS WORK/FUNCTIONS.py", line 78, in <module>
if img[x,y] == [150,150,150]:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Code:
img = cv2.imread('imj.jpg')
h ,w =img.shape[:2]
m= 0
for y in range(h):
for x in range(w):
if img[x,y] == [150,150,150]:
m+=1
print('No. of points = ' , m)
Instead of using a for loop, you should vectorize the processing using Numpy. To count the number of pixels of color intensity [150,150,150], you can use np.count_nonzero()
count = np.count_nonzero((image == [150, 150, 150]).all(axis = 2))
Here's an example. We create a black image of size [400,400] and color the bottom left corner to [150,150,150]
import numpy as np
# Create black image
image = np.zeros((400,400,3), dtype=np.uint8)
image[300:400,300:400] = (150,150,150)
We then count the number of pixels at this intensity
# Count number of pixels of specific color intensity
count = np.count_nonzero((image == [150, 150, 150]).all(axis = 2))
print(count)
10000
Finally we if wanted to change the pixels of that intensity, we can find all desired pixels and use a mask. In this case, we turn the pixels to green
# Find pixels of desired color intensity and draw onto mask
mask = (image == [150.,150.,150.]).all(axis=2)
# Apply the mask to change the pixels
image[mask] = [36,255,12]
Full code
import numpy as np
# Create black image
image = np.zeros((400,400,3), dtype=np.uint8)
image[300:400,300:400] = (150,150,150)
# Count number of pixels of specific color intensity
count = np.count_nonzero((image == [150, 150, 150]).all(axis = 2))
print(count)
# Find pixels of desired color intensity and draw onto mask
mask = (image == [150.,150.,150.]).all(axis=2)
# Apply the mask to change the pixels
image[mask] = [36,255,12]
It's not a recommended way to count the pixels having a given value, but still you can use below code for above case(same value of r, g and b):
for x in range(h):
for y in range(w):
if np.all(img[x, y]==150, axis=-1): # (img[x, y]==150).all(axis=-1)
m+=1
If you want to count pixels with different values of r, g and b, then use np.all(img[x, y]==[b_value, g_value, r_value], axis=-1), since OpenCV follows bgr order.
Alternatively, you can use np.count_nonzero(np.all(img==[b_value, g_value, r_value],axis=-1)) or simply np.count_nonzero(np.all(img==150, axis=-1)) in above case.
My code currently consists of loading the image, which is successful and I don't believe has any connection to the problem.
Then I go on to transform the color image into a np.array named rgb
# convert image into array
rgb = np.array(img)
red = rgb[:,:,0]
green = rgb[:,:,1]
blue = rgb[:,:,2]
To double check my understanding of this array, in case that may be the root of the issue, it is an array such that rgb[x-coordinate, y-coordinate, color band] which holds the value between 0-255 of either red, green or blue.
Then, my idea was to make a nested for loop to traverse all pixels of my image (620px,400px) and sort them based on the ratio of green to blue and red in an attempt to single out the greener pixels and set all others to black or 0.
for i in range(xsize):
for j in range(ysize):
color = rgb[i,j] <-- Index error occurs here
if(color[0] > 128):
if(color[1] < 128):
if(color[2] > 128):
rgb[i,j] = [0,0,0]
The error I am receiving when trying to run this is as follows:
IndexError: index 400 is out of bounds for axis 0 with size 400
I thought it may have something to do with the bounds I was giving i and j so I tried only sorting through a small inner portion of the image but still got the same error. At this point I am lost as to what is even the root of the error let alone even the solution.
In direct answer to your question, the y axis is given first in numpy arrays, followed by the x axis, so interchange your indices.
Less directly, you will find that for loops are very slow in Python and you are generally better off using numpy vectorised operations instead. Also, you will often find it easier to find shades of green in HSV colourspace.
Let's start with an HSL colour wheel:
and assume you want to make all the greens into black. So, from that Wikipedia page, the Hue corresponding to Green is 120 degrees, which means you could do this:
#!/usr/local/bin/python3
import numpy as np
from PIL import Image
# Open image and make RGB and HSV versions
RGBim = Image.open("image.png").convert('RGB')
HSVim = RGBim.convert('HSV')
# Make numpy versions
RGBna = np.array(RGBim)
HSVna = np.array(HSVim)
# Extract Hue
H = HSVna[:,:,0]
# Find all green pixels, i.e. where 100 < Hue < 140
lo,hi = 100,140
# Rescale to 0-255, rather than 0-360 because we are using uint8
lo = int((lo * 255) / 360)
hi = int((hi * 255) / 360)
green = np.where((H>lo) & (H<hi))
# Make all green pixels black in original image
RGBna[green] = [0,0,0]
count = green[0].size
print("Pixels matched: {}".format(count))
Image.fromarray(RGBna).save('result.png')
Which gives:
Here is a slightly improved version that retains the alpha/transparency, and matches red pixels for extra fun:
#!/usr/local/bin/python3
import numpy as np
from PIL import Image
# Open image and make RGB and HSV versions
im = Image.open("image.png")
# Save Alpha if present, then remove
if 'A' in im.getbands():
savedAlpha = im.getchannel('A')
im = im.convert('RGB')
# Make HSV version
HSVim = im.convert('HSV')
# Make numpy versions
RGBna = np.array(im)
HSVna = np.array(HSVim)
# Extract Hue
H = HSVna[:,:,0]
# Find all red pixels, i.e. where 340 < Hue < 20
lo,hi = 340,20
# Rescale to 0-255, rather than 0-360 because we are using uint8
lo = int((lo * 255) / 360)
hi = int((hi * 255) / 360)
red = np.where((H>lo) | (H<hi))
# Make all red pixels black in original image
RGBna[red] = [0,0,0]
count = red[0].size
print("Pixels matched: {}".format(count))
result=Image.fromarray(RGBna)
# Replace Alpha if originally present
if savedAlpha is not None:
result.putalpha(savedAlpha)
result.save('result.png')
Keywords: Image processing, PIL, Pillow, Hue Saturation Value, HSV, HSL, color ranges, colour ranges, range, prime.
How fast change pixels values? In C# what i need to do is only use GetPixel() to get pixel value and SetPixel() to change it (its pretty easy to use but slow, MarshallCopy and Lock/UnlockBits is much faster).
In this code, i marking black pixels as 1 and white pixels as 0
import tkFileDialog
import cv2
import numpy as np
from matplotlib import pyplot as plt
path = tkFileDialog.askopenfilename()
bmp = cv2.imread(path) #reading image
height, width, channels = bmp.shape
if channels == 3:
bmp = cv2.cvtColor(bmp, cv2.COLOR_BGR2GRAY) #if image have 3 channels, convert to BW
bmp = bmp.astype('uint8')
bmp = cv2.adaptiveThreshold(bmp,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.THRESH_BINARY,11,2) #Otsu thresholding
imageData = np.asarray(bmp) #get pixels values
pixelArray = [[0 for y in range(height)] for x in range(width)] #set size of array for pixels
for y in range(len(imageData)):
for x in range(len(imageData[0])):
if imageData[y][x] == 0:
pixelArray[y][x] = 1 #if black pixels = 1
else:
pixelArray[y][x] = 0 #if white pixels = 0
In c#, it can looks like this:
for (y = 0; y < bmp.Height-1; y++)
{
for (x = 0; x < bmp.Width-1; x++)
{
if (pixelArray[y, x] == 1)
newImage.SetPixel(x, y, Color.Black); //printing new bitmap
else
newImage.SetPixel(x, y, Color.White);
}
}
image2.Source = Bitmap2BitmapImage(newImage);
In the next step i will marking countour pixels as "2", but now i want to ask you, how to set new image in python from my specific value and then, display it? For experimental purpose, i want to invert image (from B&W to W&B) only by byte valuse. Can you help me how to do it?
EDIT1
I think i found a solution, but i have GREYSCALE image with one channel (i think thats how it works when i using cv2.cvtColor to convert 3 channels image to greyscale image). The function like this:
im[np.where((im == [0,0,0]).all(axis = 2))] = [0,33,166]
Could work pretty well, but how to make that function work with greyscale image? I want to set some black pixels (0) into White (255)
For a single channel image (gray scale image) use the following:
First create a copy of the gray image:
gray_2 = gray.copy()
Now assign black pixels to be white:
gray_2[np.where(gray == 0)] = 255