I am new to opencv and I couldn't find any solution for this problem of mine.
I wonder if it is possible to apply erosion/dilation only to a specific portion of an image and let the rest of the image remain as it is originally.
Just get a submatrix of the area you want to apply erode/dilate to and apply the operation in-place:
import cv2
import numpy as np
import matplotlib.pyplot as plt
im = cv2.imread('image_to_process.jpg')
roi = im[:100, :100, :]
# define your_kernel as needed
roi[:] = cv2.dilate(roi, your_kernel) # the [:] is important
Note that I use roi[:] to have the result of dilate overwrite the content of roi instead of allocating a new matrix, so that the change actually reflects on im too.
Related
As the title says, I have to take an image and write code that colors in every n-th pixel on x axis and y axis.
I've tried using for loops, but it colors in the whole axis line instead of the one pixel that i need. I either have to use OpenCV or Pillow for this task.
#pillow
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
picture = Image.open('e92m3.jpg')
picture_resized = picture.resize( (500,500) )
pixels = picture_resized.load()
#x,y
for i in range(0,500):
pixels[i,10] = (0,255,0)
for i in range(0,500):
pixels[10,i] = (255,0,0)
%matplotlib notebook
plt.imshow(picture_resized)
This is how it should approximately look like:
You really should avoid for loops with image processing in Python. They are horribly slow and inefficient. As pretty much all image processing suites use Numpy arrays to store images, you should try and use vectorised Numpy access methods such as slicing, indexing and broadcasting:
import numpy as np
import cv2
# Load image
im = cv2.imread('lena.png')
# Use Numpy indexing to make alternate rows and columns black
im[0::2,0::2] = [0,0,0]
im[1::2,1::2] = [0,0,0]
cv2.imwrite('result.png', im)
If you want to use PIL/Pillow in place of OpenCV, load and save the image like this:
from PIL import Image
# Load as PIL Image and make into Numpy array
im = np.array(Image.open('lena.png').convert('RGB'))
... process ...
# Make Numpy array back into PIL Image and save
Image.fromarray(im).save('result.png')
Maybe have a read here about indexing.
I don't think I've understood your question but here is my answer on what i understood of it.
def interval_replace(img, offset_x: int=0, interval_x: int, offset_y: int=0, interval_y: int, replace_pxl: tuple):
for y in range(offset_y, img.shape[0]):
for x in range(offset_x, img.shape[1]):
if x % interval_x == 0 and y % interval_y == 0:
img[y][x] = replace_pxl
I have an Numpy array (it's the red channel from an image).
I have masked a portion of it (making those values 0), and now I would like to find the Mode of the values in my non masked area.
The problem I'm running into is that the Mode command keeps coming back with [0]. I want to exclude the 0 values (the masked area), but I'm not sure how to do this?
This is the command I was using to try and get mode:
#mR is the Numpy Array of the Red channel with the values of the areas I don't want at 0
print(stats.mode(mR[:, :], axis=None))
Returns 0 as my Mode.
How do I exclude 0 or the masked area?
Update - Full Code:
Here's my full code using the "face" from scipy.misc - still seems slow with that image and the result is "107" which is way to high for the masked area (shadows) so seems like it's processing the whole image, not just the area in the mask.
import cv2
import numpy as np
from scipy import stats
import scipy.misc
img = scipy.misc.face()
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
r, g, b = cv2.split(img_rgb)
img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l_channel ,a_channel, b_channel = cv2.split(img_lab)
mask = cv2.inRange(l_channel, 5, 10)
cv2.imshow("mask", mask)
print(stats.mode(r[mask],axis=None))
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)
You can just mask the array and use np.histogram:
counts, bins = np.histogram(mR[mR>0], bins=np.arange(256))
# mode
modeR = np.argmax(counts)
Update:
After the OP kindly posted their full code, I can confirm that stats.mode() is either extremely slow or never in fact completes (who knows why?).
On the other hand #Quang Hoang's solution is as elegant as it is fast - and it also works for me in terms of respecting the mask.
I of course therefore throw my weight behind QH's answer.
My old answer:
Try
print(stats.mode(mR[mask],axis=None))
Except for the masking, calculating the mode of a numpy array efficiently is covered extensively here:
Most efficient way to find mode in numpy array
I'm working with Python and trying to do Otsu thresholding on an image but only inside the mask (yes, I have an image and a mask image). It means less pixel on the image will be included in the histogram for calculating the Otsu threshold.
I'm currently using the cv2.threshold function without the mask image and have no idea how to do this kind of job.
ret, OtsuMat = cv2.threshold(GaborMat, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
Since this function also incorporates the pixels outside the mask, I think it will give a less accurate threshold.
This is the example of the image and its mask:
https://drive.google.com/drive/folders/1p8JMhncJs19oOWO9RdkWuEADVGqE-gzQ?usp=sharing
Hope there is a OpenCV or other lib function to do it easily (and also with fast computing), but any kind of help will be appreciated.
I had a try at this using the threshold_otsu() method from skimage and a Numpy masked array. I don't know if there are faster ways - the skimage is normally pretty well optimised. If anyone else wants to take my sample data and try other ideas on it, please feel free - although there is a service charge of one upvote ;-)
#!/usr/bin/env python3
import cv2
import numpy as np
import numpy.ma as ma
from skimage.filters import threshold_otsu
# Set up some repeatable test data, 4 blocks 100x100 pixels each of random normal np.uint8s centred on 32, 64, 160,192
np.random.seed(42)
a=np.random.normal(size = (100,100), loc = 32,scale=10).astype(np.uint8)
b=np.random.normal(size = (100,100), loc = 64,scale=10).astype(np.uint8)
c=np.random.normal(size = (100,100), loc = 160,scale=10).astype(np.uint8)
d=np.random.normal(size = (100,100), loc = 192,scale=10).astype(np.uint8)
# Stack (concatenate) the 4 squares horizontally across the page
im = np.hstack((a,b,c,d))
# Next line is just for debug
cv2.imwrite('start.png',im)
That gives us this:
# Now make a mask revealing only left half of image, centred on 32 and 64
mask=np.zeros((100,400))
mask[:,200:]=1
masked = ma.masked_array(im,mask)
print(threshold_otsu(masked.compressed())) # Prints 47
# Now do same revealing only right half of image, centred on 160 and 192
masked = ma.masked_array(im,1-mask)
print(threshold_otsu(masked.compressed())) # Prints 175
The histogram of the test data looks like this, x-axis is 0..255
Adapting to your own sample data, I get this:
#!/usr/bin/env python3
import cv2
import numpy as np
import numpy.ma as ma
from skimage.filters import threshold_otsu
# Load images
im = cv2.imread('eye.tif', cv2.IMREAD_UNCHANGED)
mask = cv2.imread('mask.tif', cv2.IMREAD_UNCHANGED)
# Calculate Otsu threshold on entire image
print(threshold_otsu(im)) # prints 130
# Now do same for masked image
masked = ma.masked_array(im,mask>0)
print(threshold_otsu(masked.compressed())). # prints 124
I create an image and fill the pixels:
img = Image.new( 'RGB', (2000,2000), "black") # create a new black image
pixels = img.load() # create the pixel map
for i in range(img.size[0]): # for every pixel:
for j in range(img.size[1]):
#do some stuff that requires i and j as parameter
Can this be done more elegant (and may be faster, since theoretically the loops are parallelizable)?
Note: I will first answer the question, then propose an, in my opinion, better alternative
Answering the question
It is hard to give advice without knowing what changes you intend to apply and whether the loading of the image as a PIL image is part of the question or a given.
More elegant in Python-speak typically means using list comprehensions
For parallelization, you would look at something like the multiprocessing module or joblib
Depending on your method of creating / loading in images, the list_of_pixels = list(img.getdata()) and img.putdata(new_list_of_pixels) functions may be of interest to you.
An example of what this might look like:
from PIL import Image
from multiprocessing import Pool
img = Image.new( 'RGB', (2000,2000), "black")
# a function that fixes the green component of a pixel to the value 50
def update_pixel(p):
return (p[0], 50, p[2])
list_of_pixels = list(img.getdata())
pool = Pool(4)
new_list_of_pixels = pool.map(update_pixel, list_of_pixels)
pool.close()
pool.join()
img.putdata(new_list_of_pixels)
However, I don't think that is a good idea... When you see loops (and list comprehensions) over thousands of elements in Python and you have performance on your mind, you can be sure there is a library that will make this faster.
Better Alternative
First, a quick pointer to the Channel Operations module,
Since you don't specify the kind of pixel operation you intend to do and you clearly already know about the PIL library, I'll assume you're aware of it and it doesn't do what you want.
Then, any moderately complex matrix manipulation in Python will benefit from pulling in Pandas, Numpy or Scipy...
Pure numpy example:
import numpy as np
import matplotlib.pyplot as plt
#black image
img = np.zeros([100,100,3],dtype=np.uint8)
#show
plt.imshow(img)
#make it green
img[:,:, 1] = 50
#show
plt.imshow(img)
Since you are just working with a standard numpy.ndarray, you can use any of the available functionalities, such as np.vectorize, apply, map etc. To show a similar solution as above with the update_pixel function:
import numpy as np
import matplotlib.pyplot as plt
#black image
img = np.zeros([100,100,3],dtype=np.uint8)
#show
plt.imshow(img)
#make it green
def update_pixel(p):
return (p[0], 50, p[2])
green_img = np.apply_along_axis(update_pixel, 2, img)
#show
plt.imshow(green_img)
One more example, this time calculating the image content directly from the indexes, instead of from existing image pixel content (no need to create an empty image first):
import numpy as np
import matplotlib.pyplot as plt
def calc_pixel(x,y):
return np.array([100-x, x+y, 100-y])
img = np.frompyfunc(calc_pixel, 2, 1).outer(np.arange(100), np.arange(100))
plt.imshow(np.array(img.tolist()))
#note: I don't know any other way to convert a 2D array of arrays to a 3D array...
And, low and behold, scipy has methods to read and write images and inbetween, you can just use numpy to manipulate them as "classic" mult-dimensional arrays. (scipy.misc.imread depends on PIL, by the way)
More example code.
I am trying to flip a picture on its vertical axis, I am doing this in python, and using the Media module.
like this:
i try to find the relationship between the original and the flipped. since i can't go to negative coordinates in python, what i decided to do is use the middle of the picture as the reference.
so i split the picture in half,and this is what i am going to do:
[note i create a new blank picture and copy each (x,y) pixel to the corresponding to (-x,y), if the original pixel is after the middle.
if its before the middle, i copy the pixel (-x,y) to (x,y)
so i coded it in python, and this is the result.
Original:
i got this:
import media
pic=media.load_picture(media.choose_file())
height=media.get_height(pic)
width=media.get_width(pic)
new_pic=media.create_picture(width,height)
for pixel in pic:
x_org=media.get_x(pixel)
y_org=media.get_y(pixel)
colour=media.get_color(pixel)
new_pixel_0=media.get_pixel(new_pic,x_org+mid_width,y_org) #replace with suggested
#answer below
media.set_color( new_pixel_0,colour)
media.show(new_pic)
this is not what i wanted, but i am so confused, i try to find the relationship between the original pixel location and its transformed (x,y)->(-x,y). but i think that's wrong. If anyone could help me with this method it would be great full.
at the end of the day i want a picture like this:
http://www.misterteacher.com/alphabetgeometry/transformations.html#Flip
Why not just use Python Imaging Library? Flipping an image horizontally is a one-liner, and much faster to boot.
from PIL import Image
img = Image.open("AFLAC.jpg").transpose(Image.FLIP_LEFT_RIGHT)
Your arithmetic is incorrect. Try this instead...
new_pixel_0 = media.get_pixel(new_pic, width - x_org, y_org)
There is no need to treat the two halves of the image separately.
This is essentially negating the x-co-ordinate, as your first diagram illustrates, but then slides (or translates) the flipped image by width pixels to the right to put it back in the range (0 - width).
Here is a simple function to flip an image using scipy and numpy:
import numpy as np
from scipy.misc import imread, imshow
import matplotlib.pyplot as plt
def flip_image(file_name):
img = imread(file_name)
flipped_img = np.ndarray((img.shape), dtype='uint8')
flipped_img[:,:,0] = np.fliplr(img[:,:,0])
flipped_img[:,:,1] = np.fliplr(img[:,:,1])
flipped_img[:,:,2] = np.fliplr(img[:,:,2])
plt.imshow(flipped_img)
return flipped_img