Search code examples
imageopencvcommand-lineimagemagickcrop

batch crop quad images with diffenrent sizes to a circle


I have lots of images of planets in differing sizes like

enter image description here enter image description here enter image description here enter image description here

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:

enter image description here enter image description here enter image description here enter image description here

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


Solution

  • Here is one way using Python Opencv from the minEclosingCircle.

    Input:

    enter image description here

    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:

    enter image description here

    Circle on input:

    enter image description here

    Mask image:

    enter image description here

    Transparent image:

    enter image description here

    Cropped transparent image:

    enter image description here

    packages to install

    sudo apt install python3-opencv python3-sklearn python3-skimage