Cropping logos from white paper - python

I have a huge dataset of images having some logos at arbitrary places on white paper. How to retrieve coordinates (top left and bottom right) of object from the image using python?
For ex, consider this image
http://ak9.picdn.net/shutterstock/videos/5360279/thumb/3.jpg (ignore shadow)
I want to highlight egg in the image.
EDIT:
Images are hi-res & very huge in count so iterative solution takes a good amount of time. One thing i missed is that images are stored in 1-bit mode. So i think we can get better solution using numpy.

If the rest of the picture is one colour you can compare each pixel and find a different colour indicating the start of the picture like this please pay attention that I assume the top right hand corner to be the background colour, if this is not always the case, use a different approach (counting mode pixel colour for instance)!:
import numpy as np
from PIL import Image
import pprint
def get_y_top(pix, width, height, background, difference):
back_np = np.array(background)
for y in range(0, height):
for x in range(0, width):
if max(np.abs(np.array(pix[x, y]) - back_np)) > difference:
return y
def get_y_bot(pix, width, height, background, difference):
back_np = np.array(background)
for y in range(height-1, -1, -1):
for x in range(0, width):
if max(np.abs(np.array(pix[x, y]) - back_np)) > difference:
return y
def get_x_left(pix, width, height, background, difference):
back_np = np.array(background)
for x in range(0, width):
for y in range(0, height):
if max(np.abs(np.array(pix[x, y]) - back_np)) > difference:
return x
def get_x_right(pix, width, height, background, difference):
back_np = np.array(background)
for x in range(width-1, -1, -1):
for y in range(0, height):
if max(np.abs(np.array(pix[x, y]) - back_np)) > difference:
return x
img = Image.open('test.jpg')
width, height = img.size
pix = img.load()
background = pix[0,0]
difference = 20 #or whatever works for you here, use trial and error to establish this number
y_top = get_y_top(pix, width, height, background, difference)
y_bot = get_y_bot(pix, width, height, background, difference)
x_left = get_x_left(pix, width, height, background, difference)
x_right = get_x_right(pix, width, height, background, difference)
Using this information you can crop your image and save:
img = img.crop((x_left,y_top,x_right,y_bot))
img.save('test3.jpg')
Resulting in this:

For this image(the egg on the white bg):
Your can crop in the following steps:
Read and convert to gray
Threshold and Invert
Find the extreme coordinates and crop
The egg image, size of (480, 852, 3), costs 0.016s.
The code:
## Time passed: 0.016 s
#!/usr/bin/python3
# 2018/04/10 19:39:14
# 2018/04/10 20:25:36
import cv2
import numpy as np
import matplotlib.pyplot as plt
import time
ts = time.time()
## 1. Read and convert to gray
fname = "egg.jpg"
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
## 2. Threshold and Invert
th, dst = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
## 3. Find the extreme coordinates and crop
ys, xs = np.where(dst>0)
target = img[ys.min():ys.max(), xs.min():xs.max()]
te = time.time()
print("Time passed: {:.3f} s".format(te-ts))
plt.imshow(target)
plt.show()
## Time passed: 0.016 s

Related

How to remove unwanted parts in an image and then create a new small image using Python?

I want to remove the dark(black strips) and also the white curves in the image, and then align the remained parts connected in a new small-sized image, making the colored parts looks continuously. I hope can get some suggestions for solutions.
I have tried to use PIL to read the image.
I don't know how to set the right threshold and resize the image
I'm not an expert at all in image processing, but let me know if this is enough for you.
Looking at the brightness (sum of the RGB values) distribution, maybe one option is to just filter pixel based on their value:
It looks like the dark parts have a brightness below 100 (or something like that). I filtered it this way:
from PIL import Image
import numpy as np
def filter_image(img,threshold=100):
data = np.array(img.convert('RGB'))
brightness = np.sum(data,axis=2)
filtered_img = data.copy()*0
for i in range(data.shape[0]):
k = 0 # k index the columns that are bright enough
for j in range(data.shape[1]):
if brightness[i,j] > threshold:
filtered_img[i,k,:] = data[i,j,:]
k += 1 # we increment only if it's bright enough
# End of column iterator. The right side of the image is black
return Image.fromarray(filtered_img)
img = Image.open("test.png")
filtered = filter_image(img)
filtered.show()
I get the following result. I'm sure experts can do much better, but it's a start:
The following is only looking for black pixels, as can be seen by the first image, many of the pixels you want out are not black. You will need to find a way to scale up what you will take out.
Also, research will need to be done on collapsing an image, as can be seen by my collapse. Although, this image collapse may work if you are able to get rid of everything but the reddish colors. Or you can reduce by width, which is what the third picture shows.
from PIL import Image, ImageDraw
def main():
picture = Image.open('/Volumes/Flashdrive/Stack_OverFlow/imageprocessing.png', 'r')
# pix_val = list(im.getdata())
# print(pix_val)
# https://code-maven.com/create-images-with-python-pil-pillowimg = Image.new('RGB', (100, 30), color = (73, 109, 137))
blackcount = 0
pix = picture.convert('RGB') # https://stackoverflow.com/questions/11064786/get-pixels-rgb-using-pil
width, height = picture.size
img = Image.new('RGB', (width, height), color=(73, 109, 137))
newpic = []
for i in range(width):
newpictemp = []
for j in range(height):
# https://stackoverflow.com/questions/13167269/changing-pixel-color-python
r, g, b = pix.getpixel((i, j))
if r == 0 and g == 0 and b == 0:
blackcount += 1
else:
img.putpixel((i, j), (r, g, b))
newpictemp.append((r, g, b))
newpic.append(newpictemp)
img.save('pil_text.png')
newheight = int(((width * height) - blackcount) / width)
print(newheight)
img2 = Image.new('RGB', (width, newheight), color=(73, 109, 137))
for i in range(width):
for j in range(newheight):
try:
z = newpic[i][j]
img2.putpixel((i, j), newpic[i][j])
except:
continue
img2.save('pil_text2.png')
if __name__ == "__main__":
main()
No black pixels on left, removed black pixels on right, remove and resize by width (height resize shown in code)

Crop entire image with the same cropping size with PIL in python

I have some problem with my logic on PIL python. My goal is to crop one image entirely in 64x64 size from the left-top corner to botom-right corner position. I can do one time cropping operation, but when I tried to crop an image entirely with looping, I am stuck with the looping case in the middle.
In the first looping, I can crop ((0, 0, 64, 64)). But then I cannot figure the looping part to get the next 64x64s to the left and to the bottom with PIL. As the first 2-tuple is the origin position point, the next tuple is for the cropping size.
any help will be really appreciated as I am starting to learn python.
import os
from PIL import Image
savedir = "E:/Cropped/OK"
filename = "E:/Cropped/dog.jpg"
img = Image.open(filename)
width, height = img.size
start_pos = start_x, start_y = (0,0)
cropped_image_size = w, h = (64, 64)
frame_num = 1
for col_i in range (width):
for row_i in range (height):
x = start_x + col_i*w
y = start_y + row_i*h
crop = img.crop((x, y, x+w*row_i, y+h*col_i))
save_to= os.path.join(savedir, "counter_{:03}.jpg")
crop.save(save_to.format(frame_num))
frame_num += 1
You can use the range() function to do the stepping for you (in blocks of 64 in this case), so that your cropping only involves simple expressions:
import os
from PIL import Image
savedir = "E:/Cropped/OK"
filename = "E:/Cropped/dog.jpg"
img = Image.open(filename)
width, height = img.size
start_pos = start_x, start_y = (0, 0)
cropped_image_size = w, h = (64, 64)
frame_num = 1
for col_i in range(0, width, w):
for row_i in range(0, height, h):
crop = img.crop((col_i, row_i, col_i + w, row_i + h))
save_to= os.path.join(savedir, "counter_{:03}.jpg")
crop.save(save_to.format(frame_num))
frame_num += 1
Other than that, your code works as expected.

Adding borders to an image using python

I have a large number of images of a fixed size (say 500*500). I want to write a python script which will resize them to a fixed size (say 800*800) but will keep the original image at the center and fill the excess area with a fixed color (say black).
I am using PIL. I can resize the image using the resize function now, but that changes the aspect ratio. Is there any way to do this?
You can create a new image with the desired new size, and paste the old image in the center, then saving it. If you want, you can overwrite the original image (are you sure? ;o)
import Image
old_im = Image.open('someimage.jpg')
old_size = old_im.size
new_size = (800, 800)
new_im = Image.new("RGB", new_size) ## luckily, this is already black!
box = tuple((n - o) // 2 for n, o in zip(new_size, old_size))
new_im.paste(old_im, box)
new_im.show()
# new_im.save('someimage.jpg')
You can also set the color of the new border with a third argument of Image.new() (for example: Image.new("RGB", new_size, "White"))
Yes, there is.
Make something like this:
from PIL import Image, ImageOps
ImageOps.expand(Image.open('original-image.png'),border=300,fill='black').save('imaged-with-border.png')
You can write the same at several lines:
from PIL import Image, ImageOps
img = Image.open('original-image.png')
img_with_border = ImageOps.expand(img,border=300,fill='black')
img_with_border.save('imaged-with-border.png')
And you say that you have a list of images. Then you must use a cycle to process all of them:
from PIL import Image, ImageOps
for i in list-of-images:
img = Image.open(i)
img_with_border = ImageOps.expand(img,border=300,fill='black')
img_with_border.save('bordered-%s' % i)
Alternatively, if you are using OpenCV, they have a function called copyMakeBorder that allows you to add padding to any of the sides of an image. Beyond solid colors, they've also got some cool options for fancy borders like reflecting or extending the image.
import cv2
img = cv2.imread('image.jpg')
color = [101, 52, 152] # 'cause purple!
# border widths; I set them all to 150
top, bottom, left, right = [150]*4
img_with_border = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
Sources: OpenCV border tutorial and
OpenCV 3.1.0 Docs for copyMakeBorder
PIL's crop method can actually handle this for you by using numbers that are outside the bounding box of the original image, though it's not explicitly stated in the documentation. Negative numbers for left and top will add black pixels to those edges, while numbers greater than the original width and height for right and bottom will add black pixels to those edges.
This code accounts for odd pixel sizes:
from PIL import Image
with Image.open('/path/to/image.gif') as im:
old_size = im.size
new_size = (800, 800)
if new_size > old_size:
# Set number of pixels to expand to the left, top, right,
# and bottom, making sure to account for even or odd numbers
if old_size[0] % 2 == 0:
add_left = add_right = (new_size[0] - old_size[0]) // 2
else:
add_left = (new_size[0] - old_size[0]) // 2
add_right = ((new_size[0] - old_size[0]) // 2) + 1
if old_size[1] % 2 == 0:
add_top = add_bottom = (new_size[1] - old_size[1]) // 2
else:
add_top = (new_size[1] - old_size[1]) // 2
add_bottom = ((new_size[1] - old_size[1]) // 2) + 1
left = 0 - add_left
top = 0 - add_top
right = old_size[0] + add_right
bottom = old_size[1] + add_bottom
# By default, the added pixels are black
im = im.crop((left, top, right, bottom))
Instead of the 4-tuple, you could instead use a 2-tuple to add the same number of pixels on the left/right and top/bottom, or a 1-tuple to add the same number of pixels to all sides.
It is important to consider old dimension, new dimension and their difference here. If the difference is odd (not even), you will need to specify slightly different values for left, top, right and bottom borders.
Assume the old dimension is ow,oh and new one is nw,nh.
So, this would be the answer:
import Image, ImageOps
img = Image.open('original-image.png')
deltaw=nw-ow
deltah=nh-oh
ltrb_border=(deltaw/2,deltah/2,deltaw-(deltaw/2),deltah-(deltah/2))
img_with_border = ImageOps.expand(img,border=ltrb_border,fill='black')
img_with_border.save('imaged-with-border.png')
You can load the image with scipy.misc.imread as a numpy array. Then create an array with the desired background with numpy.zeros((height, width, channels)) and paste the image at the desired location:
import numpy as np
import scipy.misc
im = scipy.misc.imread('foo.jpg', mode='RGB')
height, width, channels = im.shape
# make canvas
im_bg = np.zeros((height, width, channels))
im_bg = (im_bg + 1) * 255 # e.g., make it white
# Your work: Compute where it should be
pad_left = ...
pad_top = ...
im_bg[pad_top:pad_top + height,
pad_left:pad_left + width,
:] = im
# im_bg is now the image with the background.
ximg = Image.open(qpath)
xwid,xhgt = func_ResizeImage(ximg)
qpanel_3 = tk.Frame(Body,width=xwid+10,height=xhgt+10,bg='white',bd=5)
ximg = ximg.resize((xwid,xhgt),Image.ANTIALIAS)
ximg = ImageTk.PhotoImage(ximg)
panel = tk.Label(qpanel_3,image=ximg)
panel.image = ximg
panel.grid(row = 2)
from PIL import Image
from PIL import ImageOps
img = Image.open("dem.jpg").convert("RGB")
This part will add black borders at the sides (10% of width)
img_side = ImageOps.expand(img, border=(int(0.1*img.size[0]),0,int(0.1*img.size[0]),0), fill=(0,0,0))
img_side.save("sunset-sides.jpg")
This part will add black borders at the bottom & top (10% of height)
img_updown = ImageOps.expand(img, border=(0,int(0.1*img.size[1]),0,int(0.1*img.size[1])), fill=(0,0,0))
img_updown.save("sunset-top_bottom.jpg")
This part will add black borders at the bottom,top & sides (10% of height-width)
img_updown_side = ImageOps.expand(img, border=(int(0.1*img.size[0]),int(0.1*img.size[1]),int(0.1*img.size[0]),int(0.1*img.size[1])), fill=(0,0,0))
img_updown_side.save("sunset-all_sides.jpg")
img.close()
img_side.close()
img_updown.close()
img_updown_side.close()

OpenCV Python rotate image by X degrees around specific point

I'm having a hard time finding examples for rotating an image around a specific point by a specific (often very small) angle in Python using OpenCV.
This is what I have so far, but it produces a very strange resulting image, but it is rotated somewhat:
def rotateImage( image, angle ):
if image != None:
dst_image = cv.CloneImage( image )
rotate_around = (0,0)
transl = cv.CreateMat(2, 3, cv.CV_32FC1 )
matrix = cv.GetRotationMatrix2D( rotate_around, angle, 1.0, transl )
cv.GetQuadrangleSubPix( image, dst_image, transl )
cv.GetRectSubPix( dst_image, image, rotate_around )
return dst_image
import numpy as np
import cv2
def rotate_image(image, angle):
image_center = tuple(np.array(image.shape[1::-1]) / 2)
rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
return result
Assuming you're using the cv2 version, that code finds the center of the image you want to rotate, calculates the transformation matrix and applies to the image.
Or much easier use
SciPy
from scipy import ndimage
#rotation angle in degree
rotated = ndimage.rotate(image_to_rotate, 45)
see
here
for more usage info.
def rotate(image, angle, center = None, scale = 1.0):
(h, w) = image.shape[:2]
if center is None:
center = (w / 2, h / 2)
# Perform the rotation
M = cv2.getRotationMatrix2D(center, angle, scale)
rotated = cv2.warpAffine(image, M, (w, h))
return rotated
I had issues with some of the above solutions, with getting the correct "bounding_box" or new size of the image. Therefore here is my version
def rotation(image, angleInDegrees):
h, w = image.shape[:2]
img_c = (w / 2, h / 2)
rot = cv2.getRotationMatrix2D(img_c, angleInDegrees, 1)
rad = math.radians(angleInDegrees)
sin = math.sin(rad)
cos = math.cos(rad)
b_w = int((h * abs(sin)) + (w * abs(cos)))
b_h = int((h * abs(cos)) + (w * abs(sin)))
rot[0, 2] += ((b_w / 2) - img_c[0])
rot[1, 2] += ((b_h / 2) - img_c[1])
outImg = cv2.warpAffine(image, rot, (b_w, b_h), flags=cv2.INTER_LINEAR)
return outImg
The cv2.warpAffine function takes the shape parameter in reverse order: (col,row) which the answers above do not mention. Here is what worked for me:
import numpy as np
def rotateImage(image, angle):
row,col = image.shape
center=tuple(np.array([row,col])/2)
rot_mat = cv2.getRotationMatrix2D(center,angle,1.0)
new_image = cv2.warpAffine(image, rot_mat, (col,row))
return new_image
import imutils
vs = VideoStream(src=0).start()
...
while (1):
frame = vs.read()
...
frame = imutils.rotate(frame, 45)
More: https://github.com/jrosebr1/imutils
You can simply use the imutils package to do the rotation. it has two methods
rotate: rotate the image at specified angle. however the drawback is image might get cropped if it is not a square image.
rotate_bound: it overcomes the problem happened with rotate. It adjusts the size of the image accordingly while rotating the image.
more info you can get on this blog:
https://www.pyimagesearch.com/2017/01/02/rotate-images-correctly-with-opencv-and-python/
Quick tweak to #alex-rodrigues answer... deals with shape including the number of channels.
import cv2
import numpy as np
def rotateImage(image, angle):
center=tuple(np.array(image.shape[0:2])/2)
rot_mat = cv2.getRotationMatrix2D(center,angle,1.0)
return cv2.warpAffine(image, rot_mat, image.shape[0:2],flags=cv2.INTER_LINEAR)
You need a homogenous matrix of size 2x3. First 2x2 is the rotation matrix and last column is a translation vector.
Here's how to build your transformation matrix:
# Exemple with img center point:
# angle = np.pi/6
# specific_point = np.array(img.shape[:2][::-1])/2
def rotate(img: np.ndarray, angle: float, specific_point: np.ndarray) -> np.ndarray:
warp_mat = np.zeros((2,3))
cos, sin = np.cos(angle), np.sin(angle)
warp_mat[:2,:2] = [[cos, -sin],[sin, cos]]
warp_mat[:2,2] = specific_point - np.matmul(warp_mat[:2,:2], specific_point)
return cv2.warpAffine(img, warp_mat, img.shape[:2][::-1])
You can easily rotate the images using opencv python-
def funcRotate(degree=0):
degree = cv2.getTrackbarPos('degree','Frame')
rotation_matrix = cv2.getRotationMatrix2D((width / 2, height / 2), degree, 1)
rotated_image = cv2.warpAffine(original, rotation_matrix, (width, height))
cv2.imshow('Rotate', rotated_image)
If you are thinking of creating a trackbar, then simply create a trackbar using cv2.createTrackbar() and the call the funcRotate()fucntion from your main script. Then you can easily rotate it to any degree you want. Full details about the implementation can be found here as well- Rotate images at any degree using Trackbars in opencv
Here's an example for rotating about an arbitrary point (x,y) using only openCV
def rotate_about_point(x, y, degree, image):
rot_mtx = cv.getRotationMatrix2D((x, y), angle, 1)
abs_cos = abs(rot_mtx[0, 0])
abs_sin = abs(rot_mtx[0, 1])
rot_wdt = int(frm_hgt * abs_sin + frm_wdt * abs_cos)
rot_hgt = int(frm_hgt * abs_cos + frm_wdt * abs_sin)
rot_mtx += np.asarray([[0, 0, -lftmost_x],
[0, 0, -topmost_y]])
rot_img = cv.warpAffine(image, rot_mtx, (rot_wdt, rot_hgt),
borderMode=cv.BORDER_CONSTANT)
return rot_img
you can use the following code:
import numpy as np
from PIL import Image
import math
def shear(angle,x,y):
tangent=math.tan(angle/2)
new_x=round(x-y*tangent)
new_y=y
#shear 2
new_y=round(new_x*math.sin(angle)+new_y)
#since there is no change in new_x according to the shear matrix
#shear 3
new_x=round(new_x-new_y*tangent)
#since there is no change in new_y according to the shear matrix
return new_y,new_x
image = np.array(Image.open("test.png"))
# Load the image
angle=-int(input("Enter the angle :- "))
# Ask the user to enter the angle of rotation
# Define the most occuring variables
angle=math.radians(angle)
#converting degrees to radians
cosine=math.cos(angle)
sine=math.sin(angle)
height=image.shape[0]
#define the height of the image
width=image.shape[1]
#define the width of the image
# Define the height and width of the new image that is to be formed
new_height = round(abs(image.shape[0]*cosine)+abs(image.shape[1]*sine))+1
new_width = round(abs(image.shape[1]*cosine)+abs(image.shape[0]*sine))+1
output=np.zeros((new_height,new_width,image.shape[2]))
image_copy=output.copy()
# Find the centre of the image about which we have to rotate the image
original_centre_height = round(((image.shape[0]+1)/2)-1)
#with respect to the original image
original_centre_width = round(((image.shape[1]+1)/2)-1)
#with respect to the original image
# Find the centre of the new image that will be obtained
new_centre_height= round(((new_height+1)/2)-1)
#with respect to the new image
new_centre_width= round(((new_width+1)/2)-1)
#with respect to the new image
for i in range(height):
for j in range(width):
#co-ordinates of pixel with respect to the centre of original image
y=image.shape[0]-1-i-original_centre_height
x=image.shape[1]-1-j-original_centre_width
#Applying shear Transformation
new_y,new_x=shear(angle,x,y)
new_y=new_centre_height-new_y
new_x=new_centre_width-new_x
output[new_y,new_x,:]=image[i,j,:]
pil_img=Image.fromarray((output).astype(np.uint8))
pil_img.save("rotated_image.png")

PIL Best Way To Replace Color?

I am trying to remove a certain color from my image however it's not working as well as I'd hoped. I tried to do the same thing as seen here Using PIL to make all white pixels transparent? however the image quality is a bit lossy so it leaves a little ghost of odd colored pixels around where what was removed. I tried doing something like change pixel if all three values are below 100 but because the image was poor quality the surrounding pixels weren't even black.
Does anyone know of a better way with PIL in Python to replace a color and anything surrounding it? This is probably the only sure fire way I can think of to remove the objects completely however I can't think of a way to do this.
The picture has a white background and text that is black. Let's just say I want to remove the text entirely from the image without leaving any artifacts behind.
Would really appreciate someone's help! Thanks
The best way to do it is to use the "color to alpha" algorithm used in Gimp to replace a color. It will work perfectly in your case. I reimplemented this algorithm using PIL for an open source python photo processor phatch. You can find the full implementation here. This a pure PIL implementation and it doesn't have other dependences. You can copy the function code and use it. Here is a sample using Gimp:
to
You can apply the color_to_alpha function on the image using black as the color. Then paste the image on a different background color to do the replacement.
By the way, this implementation uses the ImageMath module in PIL. It is much more efficient than accessing pixels using getdata.
EDIT: Here is the full code:
from PIL import Image, ImageMath
def difference1(source, color):
"""When source is bigger than color"""
return (source - color) / (255.0 - color)
def difference2(source, color):
"""When color is bigger than source"""
return (color - source) / color
def color_to_alpha(image, color=None):
image = image.convert('RGBA')
width, height = image.size
color = map(float, color)
img_bands = [band.convert("F") for band in image.split()]
# Find the maximum difference rate between source and color. I had to use two
# difference functions because ImageMath.eval only evaluates the expression
# once.
alpha = ImageMath.eval(
"""float(
max(
max(
max(
difference1(red_band, cred_band),
difference1(green_band, cgreen_band)
),
difference1(blue_band, cblue_band)
),
max(
max(
difference2(red_band, cred_band),
difference2(green_band, cgreen_band)
),
difference2(blue_band, cblue_band)
)
)
)""",
difference1=difference1,
difference2=difference2,
red_band = img_bands[0],
green_band = img_bands[1],
blue_band = img_bands[2],
cred_band = color[0],
cgreen_band = color[1],
cblue_band = color[2]
)
# Calculate the new image colors after the removal of the selected color
new_bands = [
ImageMath.eval(
"convert((image - color) / alpha + color, 'L')",
image = img_bands[i],
color = color[i],
alpha = alpha
)
for i in xrange(3)
]
# Add the new alpha band
new_bands.append(ImageMath.eval(
"convert(alpha_band * alpha, 'L')",
alpha = alpha,
alpha_band = img_bands[3]
))
return Image.merge('RGBA', new_bands)
image = color_to_alpha(image, (0, 0, 0, 255))
background = Image.new('RGB', image.size, (255, 255, 255))
background.paste(image.convert('RGB'), mask=image)
Using numpy and PIL:
This loads the image into a numpy array of shape (W,H,3), where W is the
width and H is the height. The third axis of the array represents the 3 color
channels, R,G,B.
import Image
import numpy as np
orig_color = (255,255,255)
replacement_color = (0,0,0)
img = Image.open(filename).convert('RGB')
data = np.array(img)
data[(data == orig_color).all(axis = -1)] = replacement_color
img2 = Image.fromarray(data, mode='RGB')
img2.show()
Since orig_color is a tuple of length 3, and data has
shape (W,H,3), NumPy
broadcasts
orig_color to an array of shape (W,H,3) to perform the comparison data ==
orig_color. The result in a boolean array of shape (W,H,3).
(data == orig_color).all(axis = -1) is a boolean array of shape (W,H) which
is True wherever the RGB color in data is original_color.
#!/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.
# change with your color
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'll need to represent the image as a 2-dimensional array. This means either making a list of lists of pixels, or viewing the 1-dimensional array as a 2d one with some clever math. Then, for each pixel that is targeted, you'll need to find all surrounding pixels. You could do this with a python generator thus:
def targets(x,y):
yield (x,y) # Center
yield (x+1,y) # Left
yield (x-1,y) # Right
yield (x,y+1) # Above
yield (x,y-1) # Below
yield (x+1,y+1) # Above and to the right
yield (x+1,y-1) # Below and to the right
yield (x-1,y+1) # Above and to the left
yield (x-1,y-1) # Below and to the left
So, you would use it like this:
for x in range(width):
for y in range(height):
px = pixels[x][y]
if px[0] == 255 and px[1] == 255 and px[2] == 255:
for i,j in targets(x,y):
newpixels[i][j] = replacementColor
If the pixels are not easily identifiable e.g you say (r < 100 and g < 100 and b < 100) also doesn't match correctly the black region, it means you have lots of noise.
Best way would be to identify a region and fill it with color you want, you can identify the region manually or may be by edge detection e.g. http://bitecode.co.uk/2008/07/edge-detection-in-python/
or more sophisticated approach would be to use library like opencv (http://opencv.willowgarage.com/wiki/) to identify objects.
This is part of my code, the result would like:
source
target
import os
import struct
from PIL import Image
def changePNGColor(sourceFile, fromRgb, toRgb, deltaRank = 10):
fromRgb = fromRgb.replace('#', '')
toRgb = toRgb.replace('#', '')
fromColor = struct.unpack('BBB', bytes.fromhex(fromRgb))
toColor = struct.unpack('BBB', bytes.fromhex(toRgb))
img = Image.open(sourceFile)
img = img.convert("RGBA")
pixdata = img.load()
for x in range(0, img.size[0]):
for y in range(0, img.size[1]):
rdelta = pixdata[x, y][0] - fromColor[0]
gdelta = pixdata[x, y][0] - fromColor[0]
bdelta = pixdata[x, y][0] - fromColor[0]
if abs(rdelta) <= deltaRank and abs(gdelta) <= deltaRank and abs(bdelta) <= deltaRank:
pixdata[x, y] = (toColor[0] + rdelta, toColor[1] + gdelta, toColor[2] + bdelta, pixdata[x, y][3])
img.save(os.path.dirname(sourceFile) + os.sep + "changeColor" + os.path.splitext(sourceFile)[1])
if __name__ == '__main__':
changePNGColor("./ok_1.png", "#000000", "#ff0000")

Categories

Resources