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:
sudo apt install python3-opencv python3-sklearn python3-skimage
that looks perfect, I saved it as
planet2png.py
and installedapt install python3-opencv python3-sklearn
, which works perfect ;) I added file input and output handling here: gist.github.com/rubo77/164e671f82f335943ab936e0ba715907I only get one error with this planet: spacetrace.org/pics/system/planet_s1_299.jpg -
crop planet_s1_299.jpg 20 74 -2 51 Traceback (most recent call last): File "planet2png.py", line 96, in <module> main(sys.argv[1:]) File "planet2png.py", line 83, in main cv2.imwrite("thresh/"+outputfile, thresh) cv2.error: OpenCV(4.2.0) ../modules/imgcodecs/src/loadsave.cpp:661: error: (-2:Unspecified error) could not find a writer for the specified extension in function 'imwrite_'
Is there a way to smooth or antialias the borders?
The error is in your environment and not having a way to write the output format. What format is that file?
Get the circle arguments from one image. Make the binary mask. Then scale the mask to the same size as each input image. Then antialias it with appropriately modified blur arguments for the image size. Larger images may need more blur sigma than smaller ones, but not by too much. You may get by with the same blur. Then put the mask into the alpha channel of the image. Then crop according to scaled crop values.