I have lots of images of planets in differing sizes like
They are all positioned exactly in the middle of the square images but with different height.
Now I want to crop them and make the black border transparent. I tried with convert (ImageMagick 6.9.10-23) like this:
for i in planet_*.jpg; do
nr=$(echo ${i/planet_/}|sed s/.jpg//g|xargs)
convert $i -fuzz 1% -transparent black trans/planet_${nr}.png
done
But this leaves some artifacts like:
Is there a command to crop all images in a circle, so the planet is untouched? (It mustn't be imagemagick).
I could also imagine a solution where I would use a larger -fuzz value and then fill all transparent pixels in the inner planet circle with black.
Those are all planets, I want to convert: download zip
Here is one way using Python Opencv from the minEclosingCircle.
Input:
import cv2
import numpy as np
import skimage.exposure
# read image
img = cv2.imread('planet.jpg')
h, w, c = img.shape
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# get contour
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# get enclosing circle
center, radius = cv2.minEnclosingCircle(big_contour)
cx = int(round(center[0]))
cy = int(round(center[1]))
rr = int(round(radius))
# draw outline circle over input
circle = img.copy()
cv2.circle(circle, (cx,cy), rr, (0, 0, 255), 1)
# draw white filled circle on black background as mask
mask = np.full((h,w), 0, dtype=np.uint8)
cv2.circle(mask, (cx,cy), rr, 255, -1)
# antialias
blur = cv2.GaussianBlur(mask, (0,0), sigmaX=2, sigmaY=2, borderType = cv2.BORDER_DEFAULT)
mask = skimage.exposure.rescale_intensity(blur, in_range=(127,255), out_range=(0,255))
# put mask into alpha channel to make outside transparent
imgT = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
imgT[:,:,3] = mask
# crop the image
ulx = int(cx-rr+0.5)
uly = int(cy-rr+0.5)
brx = int(cx+rr+0.5)
bry = int(cy+rr+0.5)
print(ulx,brx,uly,bry)
crop = imgT[uly:bry+1, ulx:brx+1]
# write result to disk
cv2.imwrite("planet_thresh.jpg", thresh)
cv2.imwrite("planet_circle.jpg", circle)
cv2.imwrite("planet_mask.jpg", mask)
cv2.imwrite("planet_transparent.png", imgT)
cv2.imwrite("planet_crop.png", crop)
# display it
cv2.imshow("thresh", thresh)
cv2.imshow("circle", circle)
cv2.imshow("mask", mask)
cv2.waitKey(0)
Threshold image:
Circle on input:
Mask image:
Transparent image:
Cropped transparent image:
packages to install
sudo apt install python3-opencv python3-sklearn python3-skimage
Related
How can I make the cv2.imshow output the same as the plt.imshow output?
# loading image
img0 = cv2.imread("image.png")
# converting to gray scale
gray = cv2.cvtColor(img0, cv2.COLOR_BGR2GRAY)
# remove noise
img = cv2.GaussianBlur(gray, (3, 3), 0)
# convolute with proper kernels
laplacian = cv2.Laplacian(img, cv2.CV_64F)
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5) # x
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5) # y
imgboth = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
plt.imshow(imgboth, cmap='gray')
plt.show()
cv2.imshow("img", cv2.resize(imgboth, (960, 540)))
cv2.waitKey(0)
cv2.destroyAllWindows()
original image
plt.output
cv2.imshow
# ...
canvas = imgboth.astype(np.float32)
canvas /= np.abs(imgboth).max()
canvas += 0.5
cv.namedWindow("canvas", cv.WINDOW_NORMAL)
cv.imshow("canvas", canvas)
cv.waitKey()
cv.destroyWindow("canvas")
only looks different because you posted thumbnails, not the original size image.
when you give imshow floating point values, which you do because laplacian and sobelx are floating point, then it assumes a range of 0.0 .. 1.0 as black .. white.
matplotlib automatically scales data. OpenCV's imshow doesn't. both behaviors have pros and cons.
I am posting to understand that is that possible to remove only black dot in the image.
Here are two methods:
Method #1: Contour filtering
We convert the image to grayscale, Otsu's threshold for a binary image, then find contours and filter using a minimum threshold area. We remove the black dots by drawing filling in the contours to effectively erase the dots
import cv2
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
if cv2.contourArea(c) < 10:
cv2.drawContours(thresh, [c], -1, (0,0,0), -1)
result = 255 - thresh
cv2.imshow('result', result)
cv2.waitKey()
Method #2: Morphological operations
Similarly, we convert to grayscale then Otsu's threshold. From here we create a kernel and perform morph open
import cv2
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
opening = 255 - cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
cv2.imshow('opening', opening)
cv2.waitKey()
I have some invoice image with some text overlapping, which make some trouble for later processing, and what I only is the text in black. some I want to remove the text which is in other colors.
is there any way to achieve this?
the image is attached as example.
I have tried to solve it with opencv, but i still can't solve this:
import numpy as np import cv2
img = cv2.imread('11.png')
lower = np.array([150,150,150])
upper = np.array([200,200,200])
mask = cv2.inRange(img, lower, upper)
res = cv2.bitwise_and(img, img, mask=mask)
cv2.imwrite('22.png',res)
[image with multiple color][1]
[1]: https://i.stack.imgur.com/nWQrV.pngstrong text
The text is darker and less saturated. And as suggested as #J.D. the HSV color space is good. But his range is wrong.
In OpenCV, the H ranges in [0, 180], while the S/V ranges in [0, 255]
Here is a colormap I made in the last year, I think it's helpful.
(1) Use cv2.inRange
(2) Just threshold the V(HSV) channel:
th, threshed = cv2.threshold(v, 150, 255, cv2.THRESH_BINARY_INV)
(3) Just threshold the S(HSV) channel:
th, threshed2 = cv2.threshold(s, 30, 255, cv2.THRESH_BINARY_INV)
The result:
The demo code:
# 2018/12/30 22:21
# 2018/12/30 23:25
import cv2
img = cv2.imread("test.png")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
mask = cv2.inRange(hsv, (0,0,0), (180, 50, 130))
dst1 = cv2.bitwise_and(img, img, mask=mask)
th, threshed = cv2.threshold(v, 150, 255, cv2.THRESH_BINARY_INV)
dst2 = cv2.bitwise_and(img, img, mask=threshed)
th, threshed2 = cv2.threshold(s, 30, 255, cv2.THRESH_BINARY_INV)
dst3 = cv2.bitwise_and(img, img, mask=threshed2)
cv2.imwrite("dst1.png", dst1)
cv2.imwrite("dst2.png", dst2)
cv2.imwrite("dst3.png", dst3)
How to detect colored patches in an image using OpenCV?
How to define a threshold value to detect only green colour objects in an image :Opencv
Converting to the HSV colorspace makes selecting colors easier.
The code below does what you want.
Result:
import numpy as np
import cv2
kernel = np.ones((2,2),np.uint8)
# load image
img = cv2.imread("image.png")
# Convert BGR to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# define range of black color in HSV
lower_val = np.array([0,0,0])
upper_val = np.array([179,100,130])
# Threshold the HSV image to get only black colors
mask = cv2.inRange(hsv, lower_val, upper_val)
# Bitwise-AND mask and original image
res = cv2.bitwise_and(img,img, mask= mask)
# invert the mask to get black letters on white background
res2 = cv2.bitwise_not(mask)
# display image
cv2.imshow("img", res)
cv2.imshow("img2", res2)
cv2.waitKey(0)
cv2.destroyAllWindows()
To change the level of black selected, tweak from the upper_val, the value currently set at 130. Higher = allow lighter shades (it's called the Value). Also the value currently at 100: lower = allow less color (actually: saturation).
Read more about the HSV colorspace here.
I always find the image below very helpfull. The bottom 'disc' is all black. As you move up in Value, lighter pixels are also selected. The pixels with low saturation stay shades of gray until white (the center), the pixels with high saturation get colored(the edge).That's why you tweak those values.
Edit: As #Silencer pointed out, my range was off. Fixed it.
I'm trying to figure out how to feather the edges of an image using Pillow with Python.
I need something like this cute cat (ignore the visible edges):
I tried im.filter(ImageFilter.BLUR) but is not what I'm looking for.
Have a look at this example:
from PIL import Image
from PIL import ImageFilter
RADIUS = 10
# Open an image
im = Image.open(INPUT_IMAGE_FILENAME)
# Paste image on white background
diam = 2*RADIUS
back = Image.new('RGB', (im.size[0]+diam, im.size[1]+diam), (255,255,255))
back.paste(im, (RADIUS, RADIUS))
# Create blur mask
mask = Image.new('L', (im.size[0]+diam, im.size[1]+diam), 255)
blck = Image.new('L', (im.size[0]-diam, im.size[1]-diam), 0)
mask.paste(blck, (diam, diam))
# Blur image and paste blurred edge according to mask
blur = back.filter(ImageFilter.GaussianBlur(RADIUS/2))
back.paste(blur, mask=mask)
back.save(OUTPUT_IMAGE_FILENAME)
Original image (author - Irene Mei):
Pasted on white background:
Blur region (paste mask):
Result:
Providing modified solution with gradient paste mask (as requested by #Crickets).
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFilter
RADIUS = 10
# Open an image
im = Image.open(INPUT_IMAGE_FILENAME)
# Paste image on white background
diam = 2*RADIUS
back = Image.new('RGB', (im.size[0]+diam, im.size[1]+diam), (255,255,255))
back.paste(im, (RADIUS, RADIUS))
# Create paste mask
mask = Image.new('L', back.size, 0)
draw = ImageDraw.Draw(mask)
x0, y0 = 0, 0
x1, y1 = back.size
for d in range(diam+RADIUS):
x1, y1 = x1-1, y1-1
alpha = 255 if d<RADIUS else int(255*(diam+RADIUS-d)/diam)
draw.rectangle([x0, y0, x1, y1], outline=alpha)
x0, y0 = x0+1, y0+1
# Blur image and paste blurred edge according to mask
blur = back.filter(ImageFilter.GaussianBlur(RADIUS/2))
back.paste(blur, mask=mask)
back.save(OUTPUT_IMAGE_FILENAME)
Paste mask:
Result:
In Matlab/Octave (Image package) is it possible to draw a slightly transparent coloured rectangle over an image region?
For example; I want to draw a red rectangle (alpha/opacity of 0.5) over the top left corner of an image.
pkg load image;
pkg load signal;
i = imread('foo.jpg');
% Somehow draw a transparent rectangle over the top left of the image
imshow(i);
You can use hold on and the property 'AlphaData' to draw a transparent overlay as follows:
image = rand(100); % a random image
imshow(image); % show the image
% create the red overlay
red = zeros(100, 100, 3);
red(:, :, 1) = 1;
% create the alpha channel with the right transparency
alpha = zeros(100); % everywhere completely transparent
alpha(1:50, 1:50) = 0.5; % except for the top left corner
hold on
h = imshow(red); % show the overlay
set(h, 'AlphaData', alpha); % apply the transparency