I've tried to learn python for the last 3 weeks, and I saw a code that I need to understand what it does.
In general, the code should somehow connect to two images, and then give me a password that I need to submit.
The code is:
#env 3.7
from PIL import Image, ImageFont
import textwrap
from pathlib import Path
def find_text_in_image(imgPath):
image = Image.open(imgPath)
red_band = image.split()[0]
xSize = image.size[0]
ySize = image.size[1]
newImage = Image.new("RGB", image.size)
imagePixels = newImage.load()
for f in range(xSize):
for j in range(zSize):
if bin(red_band.getpixel((i, j)))[-1] == '0':
imagePixels[i, j] = (255, 255, 255)
else: imagePixels[i, j] = (0,0,0)
newImgPath=str(Path(imgPath).parent.absolute())
newImage.save(newImgPath+'/text.png')
It would be lovely if someone could explain it to me.
thanks!
I'll break the above snippet into parts and explain each indivdiually.
The first block are imports. PIL is usually imported by installing the Pillow library. textwrap and pathlib are two packages included in the Python Standard Library.
#env 3.7
from PIL import Image, ImageFont
import textwrap
from pathlib import Path
The next block tells you you're about to define a function that does some image processing. I'll write more in inline comments.
def find_text_in_image(imgPath):
# open the image file given and load it as an `Image` from PIL
image = Image.open(imgPath)
# this splits the image into its Red, Green, and Blue channels
# then selects the Red
red_band = image.split()[0]
# these two lines get the size of the image, width and height
xSize = image.size[0]
ySize = image.size[1]
# this constructs a new `Image` object of the same size, but blank
newImage = Image.new("RGB", image.size)
# this makes an 3-d array of the new image's pixels
imagePixels = newImage.load()
# this loops over the width, so the iterator `f` will be the column
for f in range(xSize):
# loops over the height, so `j` will be the row
for j in range(zSize): # <-- This should probably be `ySize`. `zSize` is not defined.
# this is getting a pixel at a particular (column, row) in the red channel
# and checking if it can be binarized as 0
if bin(red_band.getpixel((i, j)))[-1] == '0':
# if so, set the same spot in the new image as white
imagePixels[i, j] = (255, 255, 255)
# if not, make it black
else: imagePixels[i, j] = (0,0,0)
# now make a new path and save the image
newImgPath=str(Path(imgPath).parent.absolute())
newImage.save(newImgPath+'/text.png')
There are major problems with this code as well. In some places you refer to zSize and i despite not defining them. Also, as a matter of practice, you can create paths with pathlib objects in the idiomatic way
newPath = Path(oldPath).with_name('new_filename.ext')
Related
I think i've conquered 95% of this short script to make images out of individual pixels, but I'd like help with using a variable as part of a filename. the line in question is as follows (i've used {} to denote the place I want to insert the variable):
img.save("new\\{i, j}.png")
The full code is
# Importing Image from PIL package
from PIL import Image
# Creating image object
im = Image.open("C:\\Users\\joeco\\Desktop\\Python-test-image\\image.jpg")
px = im.load()
# Defining image width and height
imageSizeW, imageSizeH = im.size
# Running through each pixel coord by column then row
for i in range(1, imageSizeW):
for j in range(1, imageSizeH):
# Removing non white pixels, then saving a single pixel of the colour to a new file
if px != (255, 255, 255):
img = Image.new('RGB', (1, 1), color = (px[i, j]))
img.save("new\\{i, j}.png")
You have to use the character f before a string if you want to include variables in that manner. In your example, you would use img.save(f"new\\{i}, {j}.png")
You could also use %i to save the filename like so: img.save("new\\%i, %i.png" % (i, j))
import numpy as np
from imageio import imread, imwrite
im1 = imread('https://api.sofascore.app/api/v1/team/2697/image')[...,:3]
im2 = imread('https://api.sofascore.app/api/v1/team/2692/image')[...,:3]
result = np.hstack((im1,im2))
imwrite('result.jpg', result)
Original images opening directly from the url's (I'm trying to concatenate the two images into one and keep the background white):
As can be seen both have no background, but when joining the two via Python, the defined background becomes this moss green:
I tried modifying the color reception:
im1 = imread('https://api.sofascore.app/api/v1/team/2697/image')[...,:1]
im2 = imread('https://api.sofascore.app/api/v1/team/2692/image')[...,:1]
But the result is a Black & White with the background still looking like it was converted from the previous green, even though the PNG's don't have such a background color.
How should I proceed to solve my need?
There is a 4th channel in your images - transparency. You are discarding that channel with [...,:1]. This is a mistake.
If you retain the alpha channel this will work fine:
import numpy as np
from imageio import imread, imwrite
im1 = imread('https://api.sofascore.app/api/v1/team/2697/image')
im2 = imread('https://api.sofascore.app/api/v1/team/2692/image')
result = np.hstack((im1,im2))
imwrite('result.png', result)
However, if you try to make a jpg, you will have a problem:
>>> imwrite('test.jpg', result)
OSError: JPEG does not support alpha channel.
This is correct, as JPGs do not do transparency. If you would like to use transparency and also have your output be a JPG, I suggest a priest.
You can replace the transparent pixels by using np.where and looking for places that the alpha channel is 0:
result = np.hstack((im1,im2))
result[np.where(result[...,3] == 0)] = [255, 255, 255, 255]
imwrite('result.png', result)
If you want to improve image quality, here is a solution. #Brondy
# External libraries used for
# Image IO
from PIL import Image
# Morphological filtering
from skimage.morphology import opening
from skimage.morphology import disk
# Data handling
import numpy as np
# Connected component filtering
import cv2
black = 0
white = 255
threshold = 160
# Open input image in grayscale mode and get its pixels.
img = Image.open("image.jpg").convert("LA")
pixels = np.array(img)[:,:,0]
# Remove pixels above threshold
pixels[pixels > threshold] = white
pixels[pixels < threshold] = black
# Morphological opening
blobSize = 1 # Select the maximum radius of the blobs you would like to remove
structureElement = disk(blobSize) # you can define different shapes, here we take a disk shape
# We need to invert the image such that black is background and white foreground to perform the opening
pixels = np.invert(opening(np.invert(pixels), structureElement))
# Create and save new image.
newImg = Image.fromarray(pixels).convert('RGB')
newImg.save("newImage1.PNG")
# Find the connected components (black objects in your image)
# Because the function searches for white connected components on a black background, we need to invert the image
nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(np.invert(pixels), connectivity=8)
# For every connected component in your image, you can obtain the number of pixels from the stats variable in the last
# column. We remove the first entry from sizes, because this is the entry of the background connected component
sizes = stats[1:,-1]
nb_components -= 1
# Define the minimum size (number of pixels) a component should consist of
minimum_size = 100
# Create a new image
newPixels = np.ones(pixels.shape)*255
# Iterate over all components in the image, only keep the components larger than minimum size
for i in range(1, nb_components):
if sizes[i] > minimum_size:
newPixels[output == i+1] = 0
# Create and save new image.
newImg = Image.fromarray(newPixels).convert('RGB')
newImg.save("new_img.PNG")
If you want to change the background of a Image, pixellib is the best solution because it seemed the most reasonable and easy library to use.
import pixellib
from pixellib.tune_bg import alter_bg
change_bg = alter_bg()
change_bg.load_pascalvoc_model("deeplabv3_xception_tf_dim_ordering_tf_kernels.h5")
change_bg.color_bg("sample.png", colors=(255,255,255), output_image_name="colored_bg.png")
This code requires pixellib to be higher or the same as 0.6.1
I'm newbie in PIL so for me it's hard to understand how I can do it, somebody helps me with it. In code, I only take two pictures and resized a transparent one. Now I don't know what to do next to paste it without .paste() class
def get_web_image (url):
img_data = requests.get(url).content
with open('picture1(bg)_1200x800.png', 'wb') as handler:
handler.write(img_data)
return img_data
def paste_image (source, destination, x, y, omit_color="None"):
im = Image.open(source)
pixels_newpaste = []
newsize = (200, 200)
im = im.resize(newsize)
im.save('picture2(done).png')
im.show()
paste_image ('picture2(transparent)_840x841.png', main_image, 1, 1)
main_pic()
#my_img_object = get_web_image ('https://avante.biz/wp-content/uploads/Baseball-Wallpapers/Baseball-Wallpapers-015.jpg')
I tried to make it using .getpixel(), but as I understand it requires only to draw in one color. So please help me to make this function
Here is a simplified version using these two images which are suitably resized and also partially transparent:
#!/usr/bin/env python3
from PIL import Image
# Open pitcher and pitch images
bg = Image.open('pitch.jpg')
fg = Image.open('pitcher.png').convert('RGBA')
w, h = fg.width, fg.height
# Iterate over rows and columns
for y in range(h):
for x in range(w):
# Get components of foreground pixel
r, g, b, a = fg.getpixel((x,y))
# If foreground is opaque, overwrite background with foreground
if a>128:
bg.putpixel((x,y), (r,g,b))
# Save result
bg.save('result.png')
I need to convert series of images drawn as white on black background letters to images where white and black are inverted (as negative). How can I achieve this using PIL?
Try the following from the docs: https://pillow.readthedocs.io/en/stable/reference/ImageOps.html
from PIL import Image
import PIL.ImageOps
image = Image.open('your_image.png')
inverted_image = PIL.ImageOps.invert(image)
inverted_image.save('new_name.png')
Note: "The ImageOps module contains a number of 'ready-made' image processing operations. This module is somewhat experimental, and most operators only work on L and RGB images."
If the image is RGBA transparent this will fail... This should work though:
from PIL import Image
import PIL.ImageOps
image = Image.open('your_image.png')
if image.mode == 'RGBA':
r,g,b,a = image.split()
rgb_image = Image.merge('RGB', (r,g,b))
inverted_image = PIL.ImageOps.invert(rgb_image)
r2,g2,b2 = inverted_image.split()
final_transparent_image = Image.merge('RGBA', (r2,g2,b2,a))
final_transparent_image.save('new_file.png')
else:
inverted_image = PIL.ImageOps.invert(image)
inverted_image.save('new_name.png')
For anyone working with an image in "1" mode (i.e., 1-bit pixels, black and white, stored with one pixel per byte -- see docs), you need to convert it into "L" mode before calling PIL.ImageOps.invert.
Thus:
im = im.convert('L')
im = ImageOps.invert(im)
im = im.convert('1')
now ImageOps must be:
PIL.ImageChops.invert(PIL.Image.open(imagepath))
note that this works for me in python 3.8.5
In case someone is inverting a CMYK image, the current implementations of PIL and Pillow don't seem to support this and throw an error. You can, however, easily circumvent this problem by inverting your image's individual bands using this handy function (essentially an extension of Greg Sadetsky's post above):
def CMYKInvert(img) :
return Image.merge(img.mode, [ImageOps.invert(b.convert('L')) for b in img.split()])
Of course ImageOps does its job well, but unfortunately it can't work with some modes like 'RGBA'. This code will solve this problem.
def invert(image: Image.Image) -> Image.Image:
drawer = ImageDraw.Draw(image)
pixels = image.load()
for x in range(image.size[0]):
for y in range(image.size[1]):
data = pixels[x, y]
if data != (0, 0, 0, 0) and isinstance(data, tuple):
drawer.point((x, y), (255 - data[0], 255 - data[1], 255 - data[2], data[3]))
return image
from PIL import Image
img = Image.open("archive.extension")
pixels = img.load()
for i in range(img.size[0]):
for j in range(img.size[1]):
x,y,z = pixels[i,j][0],pixels[i,j][1],pixels[i,j][2]
x,y,z = abs(x-255), abs(y-255), abs(z-255)
pixels[i,j] = (x,y,z)
img.show()
`
I am probably looking for the wrong thing in the handbook, but I am looking to take an image object and expand it without resizing (stretching/squishing) the original image.
Toy example: imagine a blue rectangle, 200 x 100, then I perform some operation and I have a new image object, 400 x 300, consisting of a white background upon which a 200 x 100 blue rectangle rests. Bonus if I can control in which direction this expands, or the new background color, etc.
Essentially, I have an image to which I will be adding iteratively, and I do not know what size it will be at the outset.
I suppose it would be possible for me to grab the original object, make a new, slightly larger object, paste the original on there, draw a little more, then repeat. It seems like it might be computationally expensive. However, I thought there would be a function for this, as I assume it is a common operation. Perhaps I assumed wrong.
The ImageOps.expand function will expand the image, but it adds the same amount of pixels in each direction.
The best way is simply to make a new image and paste:
newImage = Image.new(mode, (newWidth,newHeight))
newImage.paste(srcImage, (x1,y1,x1+oldWidth,y1+oldHeight))
If performance is an issue, make your original image bigger than needed and crop it after the drawing is done.
Based on interjays answer:
#!/usr/bin/env python
from PIL import Image
import math
def resize_canvas(old_image_path="314.jpg", new_image_path="save.jpg",
canvas_width=500, canvas_height=500):
"""
Resize the canvas of old_image_path.
Store the new image in new_image_path. Center the image on the new canvas.
Parameters
----------
old_image_path : str
new_image_path : str
canvas_width : int
canvas_height : int
"""
im = Image.open(old_image_path)
old_width, old_height = im.size
# Center the image
x1 = int(math.floor((canvas_width - old_width) / 2))
y1 = int(math.floor((canvas_height - old_height) / 2))
mode = im.mode
if len(mode) == 1: # L, 1
new_background = (255)
if len(mode) == 3: # RGB
new_background = (255, 255, 255)
if len(mode) == 4: # RGBA, CMYK
new_background = (255, 255, 255, 255)
newImage = Image.new(mode, (canvas_width, canvas_height), new_background)
newImage.paste(im, (x1, y1, x1 + old_width, y1 + old_height))
newImage.save(new_image_path)
resize_canvas()
You might consider a rather different approach to your image... build it out of tiles of a fixed size. That way, as you need to expand, you just add new image tiles. When you have completed all of your computation, you can determine the final size of the image, create a blank image of that size, and paste the tiles into it. That should reduce the amount of copying you're looking at for completing the task.
(You'd likely want to encapsulate such a tiled image into an object that hid the tiling aspects from the other layers of code, of course.)
This code will enlarge a smaller image, preserving aspect ratio, then center it on a standard sized canvas. Also preserves transparency, or defaults to gray background.
Tested with P mode PNG files.
Coded debug final.show() and break for testing. Remove lines and hashtag on final.save(...) to loop and save.
Could parameterize canvas ratio and improve flexibility, but it served my purpose.
"""
Resize ... and reconfigures. images in a specified directory
Use case: Images of varying size, need to be enlarged to exaxtly 1200 x 1200
"""
import os
import glob
from PIL import Image
# Source directory plus Glob file reference (Windows)
source_path = os.path.join('C:', os.sep, 'path', 'to', 'source', '*.png')
# List of UNC Image File paths
images = glob.glob(source_path)
# Destination directory of modified image (Windows)
destination_path = os.path.join('C:', os.sep, 'path', 'to', 'destination')
for image in images:
original = Image.open(image)
# Retain original attributes (ancillary chunks)
info = original.info
# Retain original mode
mode = original.mode
# Retain original palette
if original.palette is not None:
palette = original.palette.getdata()[1]
else:
palette = False
# Match original aspect ratio
dimensions = original.getbbox()
# Identify destination image background color
if 'transparency' in info.keys():
background = original.info['transparency']
else:
# Image does not have transparency set
print(image)
background = (64)
# Get base filename and extension for destination
filename, extension = os.path.basename(image).split('.')
# Calculate matched aspect ratio
if dimensions[2] > dimensions[3]:
width = int(1200)
modifier = width / dimensions[2]
length = int(dimensions[3] * modifier)
elif dimensions[3] > dimensions[2]:
length = int(1200)
modifier = length / dimensions[3]
width = int(dimensions[2] * modifier)
else:
width, length = (1200, 1200)
size = (width, length)
# Set desired final image size
canvas = (1200, 1200)
# Calculate center position
position = (
int((1200 - width)/2),
int((1200 - length)/2),
int((1200 - width)/2) + width,
int((1200 - length)/2) + length
)
# Enlarge original image proportionally
resized = original.resize(size, Image.LANCZOS)
# Then create sized canvas
final = Image.new(mode, canvas, background)
# Replicate original properties
final.info = info
# Replicate original palatte
if palette:
final.putpalette(palette)
# Cemter paste resized image to final canvas
final.paste(resized, position)
# Save final image to destination directory
final.show()
#final.save("{}\\{}.{}".format(destination_path, filename, extension))
break