I'm looking for an efficient way to replace certain values within a numpy image.
So far this is where I got :
def observation(self, img):
# 45 50 184
background = np.array([45, 50, 184])
# 80 0 132
border = np.array([80, 0, 132])
img = self.crop(img)
for line_index, line in enumerate(img):
for pixel_index, pixel in enumerate(line):
if not np.array_equal(pixel, background) and not np.array_equal(pixel, border):
img[line_index][pixel_index] = [254, 254, 254]
The idea is to replace all the colors that are not background or border to white.
I'm quite new to this, so I'm fairly sure that there is a more efficient way to do this.
Thanks all.
numpy.where should do the job. You have to call it twice (one for the background and one for the border) or combine the 2 conditions img != background and img != border:
np.where(np.logical_and(img!=background, img != border), img, [254, 254, 254])
See this post for a small example (possible duplicate?)
Hope it helps
Related
I am trying to extract some plot lines from the image below. As you can see one line is quite thin. My idea was to remove the noise around the lines (grid lines and text).
So far I came up with this code to remove the grid:
import numpy as np
import cv2
gray = cv2.imread('test.png')
edges = cv2.Canny(gray,50,150,apertureSize = 3)
lines = cv2.HoughLinesP(image=edges,rho=0.01,theta=np.pi/90, threshold=100,lines=np.array([]), minLineLength=100,maxLineGap=80)
a,b,c = lines.shape
for i in range(a):
cv2.line(gray, (lines[i][0][0], lines[i][0][1]), (lines[i][0][2], lines[i][0][3]), (255, 255, 255), 3, cv2.LINE_AA)
cv2.imwrite('result.png',gray)
After that i get the following result:
As you can see, there is still some noise around the plots and they are a bit cut off (doesn't have to be perfectly). Has anyone a better solution or some tips how i can improve this? Maybe remove the words first? Maybe detect the lines directly instead of removing the grid etc.?
You can segment both plots using the HSV color space and looking for the blue and orange color. This results on a pretty clean binary mask. Let's check out the code:
# Imports:
import numpy as np
import cv2
# Set image path
path = "D://opencvImages//"
fileName = "graphs.png"
# Reading an image in default mode:
inputImage = readImage(path + fileName)
# BGR to HSV:
hsvImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2HSV)
The first portion of the script converts the BGR image to the HSV color space. Next you need the color values to apply a simple binary thresholding. The tricky part is to get correct HSV values. For the blue graph, a proper Hue threshold seems to be from [85, 179] while leaving the rest of the channels opened, from [0, 255]. For the orange color, a possible Hue range could be [11, 30]. I create a list with these two thresholds:
# Array with HSV values:
hsvValues = []
# Blue range:
lowThreshold = [85, 0, 0]
highThreshold = [179, 255, 255]
# Into the list:
hsvValues.append((lowThreshold, highThreshold))
# Orange range:
lowThreshold = [11, 0, 0]
highThreshold = [30, 255, 255]
# Into the list:
hsvValues.append((lowThreshold, highThreshold))
Now, let's create the mask. Just iterate over the HSV list and apply the thresholding using the cv2.inRange function:
# Create mask:
for i in range(len(hsvValues)):
# Get current thresholds:
currentLowThres = np.array(hsvValues[i][0])
currentHighThres = np.array(hsvValues[i][2])
# Apply HSV threshold:
hsvMask = cv2.inRange(hsvImage, currentLowThres, currentHighThres)
cv2.imshow("Hsv Mask", hsvMask)
cv2.waitKey(0)
These are the two masks:
Blue plot:
Orange plot:
Now, do you want to create actual line models using this information? That's another problem. I'd be cautious to use Hough's line transform/detection. Although the masks are pretty clean, Hough's line parameter tuning is notoriously capricious (i.e., difficult and non-scalable) if you attempt to run not-so similar images through the algorithm, Additionally, I guess you could be more interested in multiple lines segments per plot instead of one continuous line, so I'd be on the lookout for a more ad-hoc approach.
Using python (openCV2, tkinter etc) I've created an app (a very amateur one) to change blue pixels to white. The images are high quality jpgs or PNGS.
The process: Search every pixel of an image and if the 'b' value of BGR is higher than x, set pixel to white (255, 255, 255).
The problem: There are about 150 pictures to process at a time, so the above process takes quite long. It's around 9 - 15 seconds per iteration depending on the images size (resizing the image speeds up the process, but not ideal).
Here is the code (with GUI and exception handling elements removed for simplicity):
for filename in listdir(sourcefolder):
# Read image and set variables
frame = imread(sourcefolder+"/"+filename)
rows = frame.shape[0]
cols = frame.shape[1]
# Search pixels. If blue, set to white.
for i in range(0,rows):
for j in range(0,cols):
if frame.item(i,j,0) > 155:
frame.itemset((i,j,0),255)
frame.itemset((i,j,1),255)
frame.itemset((i,j,2),255)
imwrite(sourcecopy+"/"+filename, frame)
#release image from memory
del frame
Any help on increasing efficiency / speed would be greatly appreciated!
Start with this image:
Then use this:
import cv2
im = cv2.imread('a.png')
# Make all pixels where Blue > 150 into white
im[im[...,0]>150] = [255,255,255]
# Save result
cv2.imwrite('result.png', im)
Use cv2.threshold to create a mask using x threshold value.
Set the color like this : img_bgr[mask == 255] = [255, 0, 0]
I have a huge dataset of images like this:
I would like to change the colors on these. All white should stay white, all purple should turn white and everything else should turn black. The desired output would look like this:
I've made the code underneath and it is doing what I want, but it takes way to long to go through the amount of pictures I have. Is there another and faster way of doing this?
path = r"C:path"
for f in os.listdir(path):
f_name = (os.path.join(path,f))
if f_name.endswith(".png"):
im = Image.open(f_name)
fn, fext = os.path.splitext(f_name)
print (fn)
im =im.convert("RGBA")
for x in range(im.size[0]):
for y in range(im.size[1]):
if im.getpixel((x, y)) == (255, 255, 255, 255):
im.putpixel((x, y),(255, 255, 255,255))
elif im.getpixel((x, y)) == (128, 64, 128, 255):
im.putpixel((x, y),(255, 255, 255,255))
else:
im.putpixel((x, y),(0, 0, 0,255))
im.show()
Your images seem to be palettised as they represent segmentations, or labelled classes and there are typically fewer than 256 classes. As such, each pixel is just a label (or class number) and the actual colours are looked up in a 256-element table, i.e. the palette.
Have a look here if you are unfamiliar with palletised images.
So, you don't need to iterate over all 12 million pixels, you can instead just iterate over the palette which is only 256 elements long...
#!/usr/bin/env python3
import sys
import numpy as np
from PIL import Image
# Load image
im = Image.open('image.png')
# Check it is palettised as expected
if im.mode != 'P':
sys.exit("ERROR: Was expecting a palettised image")
# Get palette and make into Numpy array of 256 entries of 3 RGB colours
palette = np.array(im.getpalette(),dtype=np.uint8).reshape((256,3))
# Name our colours for readability
purple = [128,64,128]
white = [255,255,255]
black = [0,0,0]
# Go through palette, setting purple to white
palette[np.all(palette==purple, axis=-1)] = white
# Go through palette, setting anything not white to black
palette[~np.all(palette==white, axis=-1)] = black
# Apply our modified palette and save
im.putpalette(palette.ravel().tolist())
im.save('result.png')
That takes 290ms including loading and saving the image.
If you have many thousands of images to do, and you are on a decent OS, you can use GNU Parallel. Change the above code to accept a command-line parameter which is the name of the image, and save it as recolour.py then use:
parallel ./recolour.py {} ::: *.png
It will keep all CPU cores on your CPU busy till they are all processed.
Keywords: Image processing, Python, Numpy, PIL, Pillow, palette, getpalette, putpalette, classes, classification, label, labels, labelled image.
If you're open to use NumPy, you can heavily speed-up pixel manipulations:
from PIL import Image
import numpy as np
# Open PIL image
im = Image.open('path/to/your/image.png').convert('RGBA')
# Convert to NumPy array
pixels = np.array(im)
# Get logical indices of all white and purple pixels
idx_white = (pixels == (255, 255, 255, 255)).all(axis=2)
idx_purple = (pixels == (128, 64, 128, 255)).all(axis=2)
# Generate black image; set alpha channel to 255
out = np.zeros(pixels.shape, np.uint8)
out[:, :, 3] = 255
# Set white and purple pixels to white
out[idx_white | idx_purple] = (255, 255, 255, 255)
# Convert back to PIL image
im = Image.fromarray(out)
That code generates the desired output, and takes around 1 second on my machine, whereas your loop code needs 33 seconds.
Hope that helps!
Hi I'm trying to built simple color identifying program. I have taken a image (yellow & pink) with and convert it in HSV color space. Then used threshold to identify yellow color region. I getting the output (black image). I want yellow region to be filled with while color and rest with black.
IplImage *imgRead= cvLoadImage("yellow.jpeg",CV_LOAD_IMAGE_COLOR);
if(!imgRead) {
fprintf(stderr, "Error in reading image\n");
exit(1);
}
IplImage *imgHsv = cvCreateImage(cvGetSize(imgRead),8, 3);
cvCvtColor(imgRead, imgHsv,CV_BGR2HSV);
IplImage *imgThreshold = cvCreateImage(cvGetSize(imgRead),8, 1);
cvInRangeS(imgHsv, cvScalar(25, 80, 80,80), cvScalar(34, 255, 255,255), imgThreshold);
cvShowImage("image",imgThreshold);
cvWaitKey(0);
In above code I had calculated HSV value for yellow as 30. (In gimp hsv value for yellow color is 60). In cvInRangeS, except for hue value I'm not sure how to specify other values for cvScalar.
What values I need to put? Am I missing anything?
I think the problem you are having is due to the scaling of the HSV data to fit in 8-bits. Normally, as I'm sure you noticed from using GIMP that HSV scales are as follows:
H -> [0, 360]
S -> [0, 100]
V -> [0, 100]
But, OpenCV remaps these values as follows:
(H / 2) -> [0, 180] (so that the H values can be stored in 8-bits)
S -> [0, 255]
V -> [0, 255]
This is why your calculated Hue value is 30 instead of 60. So, to filter out all colors except for yellow your cvInRangeS call would look something like this:
cvInRangeS(imgHsv, cvScalar(25, 245, 245, 0), cvScalar(35, 255, 255, 255), imgThreshold);
The fourth channel is unused for HSV. This call would give you 10-counts of noise in your color detector threshold for each dimension.
As mentioned by, SSteve your threshold should work, but you may need to expand your threshold boundaries to capture the yellow-ish color in your image.
Hope that helps!
I ran your code and it worked fine. Perhaps the yellow in your image isn't as yellow as you think.
Edit: The other potential difference is that I'm using OpenCV 2.3. Which version are you using?
Ok, one more edit: Have you tried looking at your yellow values? That would give you a definitive answer as to what values you should use in cvInRangeS. Add these two lines after the call to cvCvtColor:
uchar* ptr = (uchar*)(imgHsv->imageData);
printf("H: %d, S:%d, V:%d\n", ptr[0], ptr[1], ptr[2]);
For my image, I got:
H: 30, S:109, V:255
That's why your code worked for me.
I have already taken a look at this question: SO question and seem to have implemented a very similar technique for replacing a single color including the alpha values:
c = Image.open(f)
c = c.convert("RGBA")
w, h = c.size
cnt = 0
for px in c.getdata():
c.putpixel((int(cnt % w), int(cnt / w)), (255, 0, 0, px[3]))
cnt += 1
However, this is very slow. I found this recipe out on the interwebs, but have not had success using it thus far.
What I am trying to do is take various PNG images that consist of a single color, white. Each pixel is 100% white with various alpha values, including alpha = 0. What I want to do is basically colorize the image with a new set color, for instance #ff0000<00-ff>. SO my starting and resulting images would look like this where the left side is my starting image and the right is my ending image (NOTE: background has been changed to a light gray so you can see it since it is actually transparent and you wouldn't be able to see the dots on the left.)
Any better way to do this?
If you have numpy, it provides a much, much faster way to operate on PIL images.
E.g.:
import Image
import numpy as np
im = Image.open('test.png')
im = im.convert('RGBA')
data = np.array(im) # "data" is a height x width x 4 numpy array
red, green, blue, alpha = data.T # Temporarily unpack the bands for readability
# Replace white with red... (leaves alpha values alone...)
white_areas = (red == 255) & (blue == 255) & (green == 255)
data[..., :-1][white_areas.T] = (255, 0, 0) # Transpose back needed
im2 = Image.fromarray(data)
im2.show()
Edit: It's a slow Monday, so I figured I'd add a couple of examples:
Just to show that it's leaving the alpha values alone, here's the results for a version of your example image with a radial gradient applied to the alpha channel:
Original:
Result:
Try this , in this sample we set the color to black if color is not white .
#!/usr/bin/python
from PIL import Image
import sys
img = Image.open(sys.argv[1])
img = img.convert("RGBA")
pixdata = img.load()
# Clean the background noise, if color != white, then set to black.
for y in xrange(img.size[1]):
for x in xrange(img.size[0]):
if pixdata[x, y] == (255, 255, 255, 255):
pixdata[x, y] = (0, 0, 0, 255)
you can use color picker in gimp to absorb the color and see that's rgba color
The Pythonware PIL online book chapter for the Image module stipulates that putpixel() is slow and suggests that it can be sped up by inlining. Or depending on PIL version, using load() instead.