3

I have a code that computes the orientation of a figure. Based on this orientation the figure is then rotated until it is straightened out. This all works fine. What I am struggling with, is getting the center of the rotated figure to the center of the whole image. So the center point of the figure should match the center point of the whole image.

Input image: enter image description here

code:

import cv2
import numpy as np
import matplotlib.pyplot as plt

path = "inputImage.png"


image=cv2.imread(path)
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh=cv2.threshold(gray,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]

contours,hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
cnt1 = contours[0]
cnt=cv2.convexHull(contours[0])
angle = cv2.minAreaRect(cnt)[-1]
print("Actual angle is:"+str(angle))
rect = cv2.minAreaRect(cnt)

p=np.array(rect[1])

if p[0] < p[1]:
        print("Angle along the longer side:"+str(rect[-1] + 180))
        act_angle=rect[-1]+180
else:
        print("Angle along the longer side:"+str(rect[-1] + 90))
        act_angle=rect[-1]+90
#act_angle gives the angle of the minAreaRect with the vertical

if act_angle < 90:
        angle = (90 + angle)
        print("angleless than -45")

        # otherwise, just take the inverse of the angle to make
        # it positive
else:
        angle=act_angle-180
        print("grter than 90")

# rotate the image to deskew it
(h, w) = image.shape[:2]
print(h,w)
center = (w // 2, h // 2)
print(center)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(image, M, (w, h),flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)

plt.imshow(rotated)
cv2.imwrite("rotated.png", rotated)

With output:

enter image description here

As you can see the white figure is slightly placed to left, I want it to be perfectly centered. Does anyone know how this can be done?

EDIT: I have tried @joe's suggestion and subtracted the centroid coordinates, from the center of the image by dividing the width and height of the picture by 2. From this I got an offset, this had to be added to the array that describes the image. But I don't know how I add the offset to the array. How would this work with the x and y coordinates?

The code:

img = cv2.imread("inputImage")
gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray_image,127,255,0)

height, width = gray_image.shape
print(img.shape)
wi=(width/2)
he=(height/2)
print(wi,he)
M = cv2.moments(thresh)

cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])

offsetX = (wi-cX)
offsetY = (he-cY)


print(offsetX,offsetY)
print(cX,cY)
nathancy
  • 42,661
  • 14
  • 115
  • 137
Nawin Narain
  • 353
  • 1
  • 4
  • 17
  • https://stackoverflow.com/questions/31400769/bounding-box-of-numpy-array could help find the center of the non-zero rectangle – Benjamin Dec 30 '19 at 04:38
  • 1
    Use numpy.meshgrid to get matrices with x and y coordinates. Then you can flatten the arrays and create a weighted average with https://docs.scipy.org/doc/numpy-1.9.2/reference/generated/numpy.average.html Or you can use one of the many many available functions e.g. https://www.learnopencv.com/find-center-of-blob-centroid-using-opencv-cpp-python/. Once you know the centroid you can subtract it from center pixel of your image to get the offset. Then add this offset to your data. – Joe Dec 30 '19 at 07:42
  • I know how to get the centroid coordinates, but how do I subtract these from the center pixel of my image? Would this be the coordinates minus the width/2 and height/2? – Nawin Narain Dec 30 '19 at 07:56
  • I have tried your suggestion (see EDIT), adding the offset to the image array is where I get stuck. – Nawin Narain Dec 30 '19 at 08:41

3 Answers3

4

Here is one way in Python/OpenCV.

Get the bounding box for the white region from the contours. Compute the offset for the recentered region. Use numpy slicing to copy that to the center of a black background the size of the input.

Input:

enter image description here

import cv2
import numpy as np

# read image as grayscale
img = cv2.imread('white_shape.png', cv2.COLOR_BGR2GRAY)

# get shape
hh, ww = img.shape


# get contours (presumably just one around the nonzero pixels) 
contours = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
for cntr in contours:
    x,y,w,h = cv2.boundingRect(cntr)

# recenter
startx = (ww - w)//2
starty = (hh - h)//2
result = np.zeros_like(img)
result[starty:starty+h,startx:startx+w] = img[y:y+h,x:x+w]

# view result
cv2.imshow("RESULT", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

# save reentered image
cv2.imwrite('white_shape_centered.png',result)


enter image description here

fmw42
  • 46,825
  • 10
  • 62
  • 80
2

One approach is to obtain the bounding box coordinates of the binary object then crop the ROI using Numpy slicing. From here we calculate the new shifted coordinates then paste the ROI onto a new blank mask.

enter image description here

Code

import cv2
import numpy as np

# Load image as grayscale and obtain bounding box coordinates
image = cv2.imread('1.png', 0)
height, width = image.shape
x,y,w,h = cv2.boundingRect(image)

# Create new blank image and shift ROI to new coordinates
mask = np.zeros(image.shape, dtype=np.uint8)
ROI = image[y:y+h, x:x+w]
x = width//2 - ROI.shape[0]//2 
y = height//2 - ROI.shape[1]//2 
mask[y:y+h, x:x+w] = ROI

cv2.imshow('ROI', ROI)
cv2.imshow('mask', mask)
cv2.waitKey()
nathancy
  • 42,661
  • 14
  • 115
  • 137
  • This is pretty similar to what I had already proposed below. But always good to see variations. I did not explicitly crop the ROI, but did the same directly in the numpy slicing. – fmw42 Dec 30 '19 at 23:01
1

@NawinNarain, from this point onwards where you found out the relative shifts w.r.t. centroid of the image, it is very straightforward - You want to make an Affine matrix with this translations and apply cv2.warpAffine() to your image. That's -it.

T = np.float32([[1, 0, shift_x], [0, 1, shift_y]]) 

We then use warpAffine() to transform the image using the matrix, T

centered_image = cv2.warpAffine(image, T, (orig_width, orig_height))

This will transform your image so that the centroid is at the center. Hope this helps. The complete center image function will look like this:

def center_image(image):
  height, width = image.shape
  print(img.shape)
  wi=(width/2)
  he=(height/2)
  print(wi,he)

  ret,thresh = cv2.threshold(image,95,255,0)

  M = cv2.moments(thresh)

  cX = int(M["m10"] / M["m00"])
  cY = int(M["m01"] / M["m00"])

  offsetX = (wi-cX)
  offsetY = (he-cY)
  T = np.float32([[1, 0, offsetX], [0, 1, offsetY]]) 
  centered_image = cv2.warpAffine(image, T, (width, height))

  return centered_image
Ashutosh Gupta
  • 670
  • 7
  • 15