My machine learning algorithm has already learned the 70000 images in the MNIST database. I want to test it on an image not included in the MNIST dataset. However, my predict function cannot read the array representation of my test image.
How do I test my algorithm on an external image?
Why is my code failing?
PS I'm using python3
Error Received:
Traceback (most recent call last):
File "hello_world2.py", line 28, in <module>
print(sgd_clf.predict(arr))
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/linear_model/base.py", line 336, in predict
scores = self.decision_function(X)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/linear_model/base.py", line 317, in decision_function
% (X.shape[1], n_features))
ValueError: X has 15 features per sample; expecting 784
Code:
# Common Imports
import numpy as np
from sklearn.datasets import fetch_mldata
from sklearn.linear_model import SGDClassifier
from PIL import Image
from resizeimage import resizeimage
# loading and learning MNIST data
mnist = fetch_mldata('MNIST original')
x, y = mnist["data"], mnist["target"]
sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(x, y)
# loading and converting to array a non-MNIST image of a "5", which is in the same folder
img = Image.open("5.png")
arr = np.array(img)
# trying to predict that the image is a "5"
img = Image.open("5.png")
img = img.convert('L') #makes it greyscale
img = resizeimage.resize_thumbnail(img, [28,28])
arr = np.array(img)
print(sgd_clf.predict(arr)) # ERROR... why????????? How do you fix it?????
It's not simply a matter of resizing, the image needs the digit centered and white on black etc. I've been working on a function to this job. This is the current version that uses opencv, although it could do with further improvement, including using PIL instead of opencv, but it should give an idea of the steps required.
def open_as_mnist(image_path):
"""
Assume this is a color or grey scale image of a digit which has not so far been preprocessed
Black and White
Resize to 20 x 20 (digit in center ideally)
Sharpen
Add white border to make it 28 x 28
Convert to white on black
"""
# open as greyscale
image = cv2.imread(image_path, 0)
# crop to contour with largest area
cropped = do_cropping(image)
# resizing the image to 20 x 20
resized20 = cv2.resize(cropped, (20, 20), interpolation=cv2.INTER_CUBIC)
cv2.imwrite('1_resized.jpg', resized20)
# gaussian filtering
blurred = cv2.GaussianBlur(resized20, (3, 3), 0)
# white digit on black background
ret, thresh = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY_INV)
padded = to20by20(thresh)
resized28 = padded_image(padded, 28)
# normalize the image values to fit in the range [0,1]
norm_image = np.asarray(resized28, dtype=np.float32) / 255.
# cv2.imshow('image', norm_image)
# cv2.waitKey(0)
# # Flatten the image to a 1-D vector and return
flat = norm_image.reshape(1, 28 * 28)
# return flat
# normalize pixels to 0 and 1. 0 is pure white, 1 is pure black.
tva = [(255 - x) * 1.0 / 255.0 for x in flat]
return tva
def padded_image(image, tosize):
"""
This method adds padding to the image and makes it to a tosize x tosize array,
without losing the aspect ratio.
Assumes desired image is square
:param image: the input image as numpy array
:param tosize: the final dimensions
"""
# image dimensions
image_height, image_width = image.shape
# if not already square then pad to square
if image_height != image_width:
# Add padding
# The aim is to make an image of different width and height to a sqaure image
# For that first the biggest attribute among width and height are determined.
max_index = np.argmax([image_height, image_width])
# if height is the biggest one, then add padding to width until width becomes
# equal to height
if max_index == 0:
#order of padding is: top, bottom, left, right
left = int((image_height - image_width) / 2)
right = image_height - image_width - left
padded_img = cv2.copyMakeBorder(image, 0, 0,
left,
right,
cv2.BORDER_CONSTANT)
# else if width is the biggest one, then add padding to height until height becomes
# equal to width
else:
top = int((image_width - image_height) / 2)
bottom = image_width - image_height - top
padded_img = cv2.copyMakeBorder(image, top, bottom, 0, 0, cv2.BORDER_CONSTANT)
else:
padded_img = image
# now that it's a square, add any additional padding required
image_height, image_width = padded_img.shape
padding = tosize - image_height
# need to handle where padding is not divisiable by 2
left = top = int(padding/2)
right = bottom = padding - left
resized = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT)
return resized
If you want to read a picture then resize it, please try
In [1]: import PIL.Image as Image
In [2]: img = Image.open('2.jpg', mode='r')
In [3]: img.mode
Out[3]: 'RGB'
In [4]: img.size
Out[4]: (2880, 1800)
In [5]: img_new = img.resize([4000, 4000], Image.ANTIALIAS)
In [6]: img_new2 = img.resize([32, 32], Image.ANTIALIAS)
Docs are here
This is the 2.jpg, sorry, it is not a digit.
This picture is from the Internet, sorry, I forget the source.
If you encounter the mode is 'RGBA', I recommend you transfer it to 'RGB' mode,
newimg = Image.new('RGB', img.size)
newimg.paste(img, mask=img.split()[3])
return newimg
Please try this:
img = Image.open("5.png")
img = img.resize((28,28))
img = img.convert('L') #makes it greyscale
Related
I'm currently trying to create an image preprocessor that adds specific noise for AI training. In this case, I'm trying to add contour lines over the top of my input image that resemble sketch lines.
So far I have been able to get these lines drawn on my image, but they are very sharp and pixelated, which obviously doesn't resemble real artist-drawn lines. I need some way to apply a slight blur to soften these edges, but so far I have not been able to do this.
Here is a visual guide to what I am trying to achieve:
I need to find the contours in image (A) and draw them onto a new layer with alpha channel (B). I then need to blur these lines (C) and paste it back onto the original image (D).
Here is the section I'm having problems with:
# Convert the image to a NumPy array
augmented_image = np.array(augmented_image)
augmented_shape = augmented_image.shape
# Convert image back to color
grey_image = color.rgb2gray(augmented_image)
# Detect the contours of the image using the Canny edge detector
edges = feature.canny(grey_image, sigma=3)
# Create a blank image with dimensions 256 x 256
blank_image = np.zeros((256, 256, 4))
# Create a copy of the image to draw the contours on and convert to 4 layers rgba
alpha = np.ones((augmented_image.shape[0], augmented_image.shape[1], 1), dtype=augmented_image.dtype) * 255
augmented_image = np.concatenate([augmented_image, alpha], axis=2)
# Iterate over the contours
for contour in measure.find_contours(edges, 0.8):
# Set offset
offset = 10
# Select a random point along the contour
point = np.random.randint(0, len(contour))
start_row, start_col = contour[point]
start_row = start_row + offset
start_col = start_col + offset
start_row = np.clip(start_row, 0, augmented_shape[0] - 1)
start_col = np.clip(start_col, 0, augmented_shape[0] - 1)
# Select a random point along the contour that is not the same as the first point
point = np.random.randint(0, len(contour))
while point == start_row:
point = np.random.randint(0, len(contour))
end_row, end_col = contour[point]
end_row = end_row + offset
end_col = end_col + offset
end_row = np.clip(end_row, 0, augmented_shape[0] - 1)
end_col = np.clip(end_col, 0, augmented_shape[0] - 1)
# Draw the line on the image using the draw.line function
rr, cc = draw.line(int(start_row), int(start_col), int(end_row), int(end_col))
blank_image[rr, cc] = 30
# Smooth the contour lines using the gaussian function
blank_image = filters.gaussian(blank_image, sigma=1)
# Make sure image is same data-type
blank_image = blank_image.astype(augmented_image.dtype)
# Create a mask for the contour lines
blank_alpha = blank_image[:, :, 3:]
mask = np.any(blank_alpha > 0, axis=2)
# Apply the smooth image to the masked region of the original image
augmented_image[mask] = blank_image[mask]
# Convert image back to 3 layers rgb
augmented_image = augmented_image[:, :, :3]
I know that the problem lies somewhere in the 'mask' variable definition. Something about it being a boolean type just pastes a line of pure black squares on my image rather than the expected blurred line. No amount of messing with layer order or adding extra layers to copy from has fixed this.
Doing this process without trying to blur the lines works great, minus the fact that it's very pixelated and doesn't fit the style of the training data. Blurring the image without trying to re-combine anything produces an adequate blurred line as well, however the entire image is blurred.
Here's what I can produce without the blurring process, and a rough idea of what I would like the final product to look like (made in Photoshop)
It's only when I try to mask and combine that this becomes a problem. I will post the full code below for anyone to run on their own system:
import random
import numpy as np
import skimage
from skimage.transform import rotate, resize
from skimage import draw, feature, color, measure, filters, util
from skimage.util import random_noise
import PIL
from PIL import Image
import os
import argparse
import cv2
from matplotlib import pyplot as plt
import matplotlib.image as mpimg
from pathlib import Path
import imghdr
# Set parser args
parser = argparse.ArgumentParser()
parser.add_argument("--dirty_dir", help="path to folder containing dirty images")
parser.add_argument("--clean_dir", help="path to folder containing clean images")
parser.add_argument("--dirty_savedir", help="path to dirty output folder")
parser.add_argument("--clean_savedir", help="path to clean output folder")
a = parser.parse_args()
# Set folder paths
dirty_dir = a.dirty_dir + '/'
clean_dir = a.clean_dir + '/'
dirty_savedir = a.dirty_savedir + '/'
clean_savedir = a.clean_savedir + '/'
print(f"Source Folder: {dirty_dir}")
print(f"Source Folder: {clean_dir}")
print(f"Output Folder: {dirty_savedir}")
print(f"Output Folder: {clean_savedir}")
def augment_image(image, filename, clean_dir):
for i in range(8):
# Create list for clean imgs
clean_list = []
# Randomly select a 256x256 region
w, h = image.size[0], image.size[1]
print(w,h)
top = random.randint(0, h - 256)
left = random.randint(0, w - 256)
right = left + 256
bottom = top + 256
dims = [left, top, right, bottom]
print(f'{filename} dimensions:{dims}')
# Add dimensions to clean_list
clean_list.extend(dims)
augmented_image = image.crop((dims))
print(f'{filename} shape: {augmented_image.size}')
# Randomly rotate the image by 90, 180, or 270 degrees
angle = random.choice([0, 90, 180, 270])
augmented_image = augmented_image.rotate(angle)
# Add angle to list
clean_list.append(angle)
# Randomly flip the image horizontally
flip_lr = random.choice([True, False])
if flip_lr == True:
augmented_image = augmented_image.transpose(Image.FLIP_LEFT_RIGHT)
clean_list.append("flip_lr")
else:
clean_list.append("none")
# Randomly flip the image vertically
flip_tb = random.choice([True, False])
if flip_tb == True:
augmented_image = augmented_image.transpose(Image.FLIP_TOP_BOTTOM)
clean_list.append("flip_tb")
else:
clean_list.append("none")
# Convert the image to a NumPy array
augmented_image = np.array(augmented_image)
augmented_shape = augmented_image.shape
# Convert image back to color
grey_image = color.rgb2gray(augmented_image)
# Detect the contours of the image using the Canny edge detector
edges = feature.canny(grey_image, sigma=3)
# Create a blank image with dimensions 256 x 256
blank_image = np.zeros((256, 256, 4))
# Create a copy of the image to draw the contours on and convert to 4 layers rgba
alpha = np.ones((augmented_image.shape[0], augmented_image.shape[1], 1), dtype=augmented_image.dtype) * 255
augmented_image = np.concatenate([augmented_image, alpha], axis=2)
# Iterate over the contours
for contour in measure.find_contours(edges, 0.8):
# Set offset
offset = 10
# Select a random point along the contour
point = np.random.randint(0, len(contour))
start_row, start_col = contour[point]
start_row = start_row + offset
start_col = start_col + offset
start_row = np.clip(start_row, 0, augmented_shape[0] - 1)
start_col = np.clip(start_col, 0, augmented_shape[0] - 1)
# Select a random point along the contour that is not the same as the first point
point = np.random.randint(0, len(contour))
while point == start_row:
point = np.random.randint(0, len(contour))
end_row, end_col = contour[point]
end_row = end_row + offset
end_col = end_col + offset
end_row = np.clip(end_row, 0, augmented_shape[0] - 1)
end_col = np.clip(end_col, 0, augmented_shape[0] - 1)
# Draw the line on the image using the draw.line function
rr, cc = draw.line(int(start_row), int(start_col), int(end_row), int(end_col))
blank_image[rr, cc] = 30
# Smooth the contour lines using the gaussian function
blank_image = filters.gaussian(blank_image, sigma=1)
# Make sure image is same data-type
blank_image = blank_image.astype(augmented_image.dtype)
# Create a mask for the contour lines
blank_alpha = blank_image[:, :, 3:]
mask = np.any(blank_alpha > 0, axis=2)
# Apply the smooth image to the masked region of the original image
augmented_image[mask] = blank_image[mask]
# Convert image back to 3 layers rgb
augmented_image = augmented_image[:, :, :3]
## Add more noise types (lines, wrinkles, color)/make noise random chance to occur ##
# Add random noise to the image
noise = random_noise(augmented_image, mode='pepper', amount=0.011)
# Convert the noisy image back to a PIL image
augmented_image = np.random.random_sample(augmented_image.shape) * 255
augmented_image = np.array(255 * noise, dtype=np.uint8)
augmented_image = Image.fromarray(augmented_image)
# Save file
augmented_image.save(dirty_savedir + '_' + str(i) + '_' + filename)
print(clean_list)
# Function to mirror edits onto clean images
def clean_aug(clean_dir, clean_list):
# Open clean directory
for filename in os.listdir(f"{clean_dir}"):
# Rule out any weird Mac files
if not filename.startswith("._"):
with Image.open(clean_dir + filename) as image:
# Define clean dimensions
clean_dims = clean_list[0:4]
# Crop image
clean_augmented = image.crop((clean_dims))
# Rotate clean image
clean_augmented = clean_augmented.rotate(clean_list[4])
# Flip clean image
if clean_list[5] == 'flip_lr':
clean_augmented = clean_augmented.transpose(Image.FLIP_LEFT_RIGHT)
if clean_list[6] == 'flip_tb':
clean_augmented = clean_augmented.transpose(Image.FLIP_TOP_BOTTOM)
# Save clean images
clean_augmented.save(clean_savedir + '_' + str(i) + '_' + filename)
print("Clean alterations copied successfully")
clean_aug(clean_dir, clean_list)
# Clean up unnecessary files
def file_scrub():
dirty_dir = dirty_savedir
image_extensions = [".png", ".jpg"] # add there all your images file extensions
img_type_accepted_by_tf = ["bmp", "gif", "jpeg", "png"]
for filepath in Path(dirty_dir).rglob("*"):
if filepath.suffix.lower() in image_extensions:
img_type = imghdr.what(filepath)
if img_type is None:
print(f"{filepath} is not an image")
elif img_type not in img_type_accepted_by_tf:
print(f"{filepath} is a {img_type}, not accepted by TensorFlow")
def image_aug(dirty_dir, clean_dir):
for filename in os.listdir(f"{dirty_dir}"):
# Check if the filename starts with "._"
if not filename.startswith("._"):
with Image.open(dirty_dir + filename) as image:
# Open the image
augment_image(image, filename, clean_dir)
image_aug(dirty_dir, clean_dir)
file_scrub()
Apologies for the cumbersome codebase. I just wanted to minimize the risk of discrepancies arising from an attempt at concatenating my script. If there is any clarification I can provide please let me know!
I've bellow function:
def alphaMerge(small_foreground, background, top, left):
result = background.copy()
fg_b, fg_g, fg_r, fg_a = cv.split(small_foreground)
print(fg_b, fg_g, fg_r, fg_a)
fg_a = fg_a / 255.0
label_rgb = cv.merge([fg_b * fg_a, fg_g * fg_a, fg_r * fg_a])
height, width = small_foreground.shape[0], small_foreground.shape[1]
part_of_bg = result[top:top + height, left:left + width, :]
bg_b, bg_g, bg_r = cv.split(part_of_bg)
part_of_bg = cv.merge([bg_b * (1 - fg_a), bg_g * (1 - fg_a), bg_r * (1 - fg_a)])
cv.add(label_rgb, part_of_bg, part_of_bg)
result[top:top + height, left:left + width, :] = part_of_bg
return result
if __name__ == '__main__':
folder_dir = r"C:\photo_datasets\products_small"
logo = cv.imread(r"C:\Users\PiotrSnella\photo_datasets\discount.png", cv.IMREAD_UNCHANGED)
for images in os.listdir(folder_dir):
input_path = os.path.join(folder_dir, images)
image_size = os.stat(input_path).st_size
if image_size < 8388608:
img = cv.imread(input_path, cv.IMREAD_UNCHANGED)
height, width, channels = img.shape
if height > 500 and width > 500:
result = alphaMerge(logo, img, 0, 0)
cv.imwrite(r'C:\photo_datasets\products_small_output_cv\{}.png'.format(images), result)
I want to combine two pictures, one with the logo which I would like to apply on full dataset from folder products_small. I'm getting a error part_of_bg = cv.merge([bg_b * (1 - fg_a), bg_g * (1 - fg_a), bg_r * (1 - fg_a)]) ValueError: operands could not be broadcast together with shapes (720,540) (766,827)
I tried other combining options and still get the error about problem with shapes, the photo could be a problem or something with the code?
Thank you for your help guys :)
Here is one way to do that in Python/OpenCV. I will place a 20% resized logo onto the pants image at coordinates 660,660 on the right side pocket.
Read the background image (pants)
Read the foreground image (logo) unchanged to preserve the alpha channel
Resize the foreground (logo) to 20%
Create a transparent image the size of the background image
Insert the resized foreground (logo) into the transparent image at the desired location
Extract the alpha channel from the inserted, resized foreground image
Extract the base BGR channels from the inserted, resized foreground image
Blend the background image and the base BGR image using the alpha channel as a mask using np.where(). Note all images must be the same dimensions and 3 channels
Save the result
Background Image:
Foreground Image:
import cv2
import numpy as np
# read background image
bimg = cv2.imread('pants.jpg')
hh, ww = bimg.shape[:2]
# read foreground image
fimg = cv2.imread('flashsale.png', cv2.IMREAD_UNCHANGED)
# resize foreground image
fimg_small = cv2.resize(fimg, (0,0), fx=0.2, fy=0.2)
ht, wd = fimg_small.shape[:2]
# create transparent image
fimg_new = np.full((hh,ww,4), (0,0,0,0), dtype=np.uint8)
# insert resized image into transparent image at desired coordinates
fimg_new[660:660+ht, 660:660+wd] = fimg_small
# extract alpha channel from foreground image as mask and make 3 channels
alpha = fimg_new[:,:,3]
alpha = cv2.merge([alpha,alpha,alpha])
# extract bgr channels from foreground image
base = fimg_new[:,:,0:3]
# blend the two images using the alpha channel as controlling mask
result = np.where(alpha==(0,0,0), bimg, base)
# save result
cv2.imwrite("pants_flashsale.png", result)
# show result
cv2.imshow("RESULT", result)
cv2.waitKey(0)
Result:
This just requires some multiplication and subtraction.
Your overlay has an actual alpha channel, not just a boolean mask. You should use it. It makes edges look better than just a hard boolean mask.
I see one issue with your overlay: it doesn't have any "shadow" to give the white text contrast against a potentially white background.
When you resize RGBA data, it's not trivial. You'd better export the graphic from your vector graphics program in the desired resolution in the first place. Resizing after the fact requires operations to make sure partially transparent pixels (neither 100% opaque nor 100% transparent) are calculated properly so undefined "background" from the fully transparent areas of the overlay image is not mixed into those partially transparent pixels.
base = cv.imread("U3hRd.jpg")
overlay = cv.imread("OBxGQ.png", cv.IMREAD_UNCHANGED)
(bheight, bwidth) = base.shape[:2]
(oheight, owidth) = overlay.shape[:2]
print("base:", bheight, bwidth)
print("overlay:", oheight, owidth)
# place overlay in center
#ox = (bwidth - owidth) // 2
#oy = (bheight - oheight) // 2
# place overlay in top left
ox = 0
oy = 0
overlay_color = overlay[:,:,:3]
overlay_alpha = overlay[:,:,3] * np.float32(1/255)
# "unsqueeze" (insert 1-sized color dimension) so numpy broadcasting works
overlay_alpha = np.expand_dims(overlay_alpha, axis=2)
composite = base.copy()
base_roi = base[oy:oy+oheight, ox:ox+owidth]
composite_roi = composite[oy:oy+oheight, ox:ox+owidth]
composite_roi[:,:] = overlay_color * overlay_alpha + base_roi * (1 - overlay_alpha)
This is what you wanted on top left corner. Noticed, the logo on white foreground doesn't work on background on pant.jpg.
Just 17 lines of codes compared to
import cv2
import numpy as np
img1 = cv2.imread('pant.jpg')
overlay_img1 = np.ones(img1.shape,np.uint8)*255
img2 = cv2.imread('logo3.png')
rows,cols,channels = img2.shape
overlay_img1[0:rows, 0:cols ] = img2
img2gray = cv2.cvtColor(overlay_img1,cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray,220,255,cv2.THRESH_BINARY_INV)
mask_inv = cv2.bitwise_not(mask)
temp1 = cv2.bitwise_and(img1,img1,mask = mask_inv)
temp2 = cv2.bitwise_and(overlay_img1,overlay_img1, mask = mask)
result = cv2.add(temp1,temp2)
cv2.imshow("Result",result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
Logo resize(320x296):
The original image is this:
Using this website tool with these settings:
The final result that I am trying to reproduce (the only difference is that I'm going to add a readjustment to the image that is on top, have 1080 width too) here is this:
It is clear that there is a zoom effect in the blurred image, so when I use resize to keep the aspect ratio and the highest quality possible, I use im = im.resize((1080,1080), resample=Image.Resampling.LANCZOS), but as I don't want that, I removed resample=Image.Resampling.LANCZOS imagining that the image would be generated without proportion generating a zoom:
from PIL import Image, ImageFilter, ImageChops
import numpy
import cv2
def remove_border(file_img):
im = Image.open(file_img)
bg = Image.new("RGB", im.size, im.getpixel((0,0)))
diff = ImageChops.difference(im.convert("RGB"), bg)
diff = ImageChops.add(diff, diff, 2.0, -30)
bbox = diff.getbbox()
if bbox:
return im.crop(bbox)
def resize_blur(img_blur,sizers):
img_blur = img_blur.resize(sizers, resample=Image.Resampling.LANCZOS)
img_blur = img_blur.filter(ImageFilter.GaussianBlur(10))
return img_blur
def resize_width_main(img_border,size_width):
img_width = img_border
basewidth = size_width
wpercent = (basewidth/float(img_width.size[0]))
hsize = int((float(img_width.size[1])*float(wpercent)))
img_width = img_width.resize((basewidth,hsize), Image.Resampling.LANCZOS)
return img_width
def center_overlay(name_file,overlay,background):
img = numpy.asarray(overlay)
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
h, w = img.shape[:2]
back = numpy.asarray(background)
back = cv2.cvtColor(back, cv2.COLOR_RGB2BGR)
hh, ww = back.shape[:2]
yoff = round((hh-h)/2)
xoff = round((ww-w)/2)
result = back.copy()
result[yoff:yoff+h, xoff:xoff+w] = img
cv2.imwrite(name_file, result)
def main():
img_border = remove_border('resized_download.png')
img_blur = resize_blur(img_border, (1080,1080))
img_width = resize_width_main(img_border, 1080)
center_overlay('resized.png', img_width, img_blur)
if __name__ == '__main__':
main()
But the current result is this:
A few observations.
Your remove_border function isn't doing a lot; when I tried it with your test image, all it did was remove 9 pixels from the left side. Maybe it has a bug. At the risk of ignoring the lesson of Chesterton's Fence I'd say you don't need it and could eliminate it entirely.
Blurring before you resize makes more sense than doing it the other way around.
When you resize a blurred image, you don't need to worry much about the quality of the resampling filter. I wouldn't use NEAREST, but BILINEAR should be perfectly adequate and reasonably fast.
When you resize an image, the old and new size should have the same aspect ratio or you will get distortion. The resample method makes no difference in this. You are resizing a rectangle into a square, resulting in bad distortion. You can crop your original to the desired aspect ratio before you resize, or crop it after you resize. It will be more efficient to crop first.
def resize_to_target(im, target_size, resample=Image.BILINEAR):
''' Resize an image to a target size. If the aspect ratio
of the original image is different from the target, the
image will be cropped to the destination aspect ratio
before resizing.
'''
if (im.size[0] / im.size[1]) < (target_size[0] / target_size[1]):
# if (im.size[0] * target_size[1]) != (target_size[0] * im.size[1]):
# Existing image is narrower, crop top and bottom
crop_height = round(im.size[0] * target_size[1] / target_size[0])
if crop_height < im.size[1]:
top = (im.size[1] - crop_height) // 2
bottom = top + crop_height
im = im.crop((0, top, im.size[0], bottom))
else:
# existing image is wider, crop left and right
crop_width = round(im.size[1] * target_size[0] / target_size[1])
if crop_width < im.size[0]:
left = (im.size[0] - crop_width) // 2
right = left + crop_width
im = im.crop((left, 0, right, im.size[1]))
return im.resize(target_size, resample=resample)
Hello I want to reflect an object in the image as in this image[enter image description here][1]
[1]: https://i.stack.imgur.com/N9J3I.jpg How can I get this kind of result?
It is possible that OpenCV does not have good solutions for this, take a closer look at Pillow.
from PIL import Image, ImageFilter
def drop_shadow(image, iterations=3, border=8, offset=(5,5), background_colour=0xffffff, shadow_colour=0x444444):
shadow_width = image.size[0] + abs(offset[0]) + 2 * border
shadow_height = image.size[1] + abs(offset[1]) + 2 * border
shadow = Image.new(image.mode, (shadow_width, shadow_height), background_colour)
shadow_left = border + max(offset[0], 0)
shadow_top = border + max(offset[1], 0)
shadow.paste(shadow_colour, [shadow_left, shadow_top, shadow_left + image.size[0], shadow_top + image.size[1]])
for i in range(iterations):
shadow = shadow.filter(ImageFilter.BLUR)
img_left = border - min(offset[0], 0)
img_top = border - min(offset[1], 0)
shadow.paste(image, (img_left, img_top))
return shadow
drop_shadow(Image.open('boobs.jpg')).save('shadowed_boobs.png')
Here is one way to do the reflection in Python/OpenCV.
One flips the image. Then makes a vertical ramp (gradient) image and puts that into the alpha channel of the flipped image. Then one concatenates the original and the flipped images.
Input:
import cv2
import numpy as np
# set top and bottom opacity percentages
top = 85
btm = 15
# load image
img = cv2.imread('bear2.png')
hh, ww = img.shape[:2]
# flip the input
flip = np.flip(img, axis=0)
# add opaque alpha channel to input
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# make vertical gradient that is bright at top and dark at bottom as alpha channel for the flipped image
gtop = 255*top//100
gbtm = 255*btm//100
grady = np.linspace(gbtm, gtop, hh, dtype=np.uint8)
gradx = np.linspace(1, 1, ww, dtype=np.uint8)
grad = np.outer(grady, gradx)
grad = np.flip(grad, axis=0)
# alternate method
#grad = np.linspace(0, 255, hh, dtype=np.uint8)
#grad = np.tile(grad, (ww,1))
#grad = np.transpose(grad)
#grad = np.flip(grad, axis=0)
# put the gradient into the alpha channel of the flipped image
flip = cv2.cvtColor(flip, cv2.COLOR_BGR2BGRA)
flip[:,:,3] = grad
# concatenate the original and the flipped versions
result = np.vstack((img, flip))
# save output
cv2.imwrite('bear2_vertical_gradient.png', grad)
cv2.imwrite('bear2_reflection.png', result)
# Display various images to see the steps
cv2.imshow('flip',flip)
cv2.imshow('grad',grad)
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Ramped (Gradient) Image:
Result:
I have an image (front facing man) with 4 different colors (background, hair, skin-tone, and cloth). I used k-mean with k=4, and image is segmented. Now what I want to do is extract out the hair out of the image.
I used canny edge detection to detect edge, which helped to detect the point in hair area(Pointed out by red dot). Now, I want to extract hair area, as the member of k-mean pointed out by the red dot. Is it possible?
Or is there any other way to extract out hair area from image of a person?
Code done till now is:
import cv2
import numpy as np
image1 = cv2.imread('Test1.jpg')
#Resizing Image for fixed width
def image_resize(image1, width = None, height = None, inter =
cv2.INTER_AREA):
# initialize the dimensions of the image to be resized and
# grab the image size
dim = None
(h, w) = image1.shape[:2]
# if both the width and height are None, then return the
# original image
if width is None and height is None:
return image1
# check to see if the width is None
if width is None:
# calculate the ratio of the height and construct the
# dimensions
r = height / float(h)
dim = (int(w * r), height)
# otherwise, the height is None
else:
# calculate the ratio of the width and construct the
# dimensions
r = width / float(w)
dim = (width, int(h * r))
# resize the image
resized = cv2.resize(image1, dim, interpolation = inter)
# return the resized image
return resized
img1 = image_resize(image1, width = 500)
cv2.imshow("Resized", img1)
cv2.waitKey(0)
#Detecting Edge of image
canny = cv2.Canny(img1, 100, 150)
cv2.imshow("Edge", canny)
cv2.waitKey(0)
coords = np.nonzero(canny)
topmost_y = np.min(coords[0])
#Blurring effect
img2 = cv2.medianBlur(img1, 5)
cv2.imshow("Blurred", img2)
cv2.waitKey(0)
#K-mean approach
Z = img2.reshape((-1,3))
Z = np.float32(Z)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K=4
ret, label1, center1 = cv2.kmeans(Z, K, None,
criteria, 10,
cv2.KMEANS_RANDOM_CENTERS)
center1 = np.uint8(center1)
res1 = center1[label1.flatten()]
output1 = res1.reshape((img2.shape))
cv2.circle(output1, (250, topmost_y + 20), 5, (0,0,255), -1)
cv2.imshow("k = 4", output1)
cv2.waitKey(0)
cv2.destroyAllWindows()
Images:
,
,
,
,
Given the code you already have you can get the xy coordinates of the cluster to which the hair belongs with just a few extra lines. You can also create an image that shows only the hair's cluster:
# find the index of the cluster of the hair
mask = label1.reshape(output1.shape[:-1])
khair = mask[(topmost_y + 20, 250)]
# get a mask that's True at all of the indices of hair's group
hairmask = mask==khair
# get the hair's cluster's xy coordinates
xyhair = hairmask.nonzero()
# plot an image with only the hair's cluster on a white background
cv2.imwrite("khair.jpg", np.where(hairmask[..., None], img1, [255,255,255]))
Here's what the hair's cluster looks like:
Once you have the hair's cluster, you can then find the blob that represents "just the hair". Here's how you'd do that:
import scipy.ndimage as snd
# label all connected blobs in hairmask
bloblab = snd.label(hairmask, structure=np.ones((3,3)))[0]
# create a mask for only the hair
haironlymask = bloblab == bloblab[topmost_y + 20, 250]
# get an image with just the hair and then crop it
justhair = np.where(haironlymask[..., None], img1, [255,255,255])
nz = haironlymask.nonzero()
justhair = justhair[nz[0].min():nz[0].max(), nz[1].min():nz[1].max()]
# save the image of just the hair on a white background
cv2.imwrite("justhair.jpg", justhair)
and here's the image of your hair by itself:
Now that you have one point in this hair region, propagate this point to all the other points.
The pseudo code would be:
set = red point
while set of hair doesn't change:
add all points (i-1, j) (i+1, j) (i, j-1) (i, j+1) to the set
intersect the set with the mask of brown points
At the end, you will have a mask with the hair.
You can do that easily in numpy by starting with a Boolean image with just one True element at the red dot and then use |= and &= operators. I suspect OpenCV also has this kind of morphological dilation operator.