Transformations with OpenCV. A guide on how to transform images with... | by Thiago Carvalho | Towards Data Science

If you ever had to use an image enhancer that could bitmap, you probably know the struggle. In this article, we’ll get a look at transformations and explore things like moving, rotating, cropping, and resizing.
We’ll use OpenCV, NumPy, and Matplotlib for the examples.
import cv2import numpy as npimport matplotlib.pyplot as plt
Here, I went through some basics of OpenCV, such as reading, displaying, and modifying a few properties of images. The examples in this article will go from there, but I don’t think you need to read it to keep up with this.
Starting with something simple, we’ll read the image and convert the sequence of colors from BGR to RGB.
Then we’ll build a transformation matrix that will contain the information to transform the image. For simplification, we can think of this matrix as:
[[size, rotation, location], ←x-axis[rotation, size, location]] ←y-axis
Where a default matrix or one that wouldn’t change anything is:
[[1, 0, 0] [0, 1, 0]]
That means, 100% of the size, zero rotation, and no change in the location — Again, I’m oversimplifying.
After we figure out the matrix, we can get the height and width of the picture with .shape, and use .warpAffine to transform the image.
Let’s check an example without any transformation, to get an idea of the process.
# read img and convert colorimg = cv2.imread('img3.jpeg')img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# build a transformation matrixmatrix = [[1, 0, 0], #x           [0, 1, 0]] #yt = np.float32(matrix)# get the sizesh, w = img.shape[:2]# transformimg = cv2.warpAffine(img, t, (w, h))# plotfig, ax = plt.subplots(1, figsize=(12,8))ax.axis('off')   plt.imshow(img)
Alright, we can try moving it. We’ll repeat everything but with a different matrix.
# Change locationmatrix = [[1, 0, 300], #x           [0, 1, -300]] #y
Now, let’s change everything, just to see how it works.
# Change size, rotation, and locationmatrix = [[0.5, 0.3, 450], #x           [-0.3, 0.5, 600]] #y
Interesting. You might have noticed that we have two different ‘sizes’ — Let’s check what’s their difference.
We can scale the width by 0.5 in the matrix.
matrix = [[0.5, 0, 0], #x           [0, 1, 0]]   #y
And we can divide the width by 2 in .warpAffine.
img = cv2.warpAffine(img, T, (int(w/2), h))
I guess it’s essential to understand the transformation matrix, but it’s somewhat over-complicated.
OpenCV has some more convenient ways to build it like the method getRotationMatrix2D, where we can set the desired location of the image, the rotation in angles, and the scaling factor in a single method.
img = cv2.imread('Images/3.jpg')img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)h, w = img.shape[:2]# (w/2, h/2) -> center of the imageT = cv2.getRotationMatrix2D((w/2, h/2), 90, .5)img = cv2.warpAffine(img, T, (w, h))fig, ax = plt.subplots(1, figsize=(12,8))ax.axis('off')   plt.imshow(img)
If we just need to turn the whole picture 90 degrees, .trasponse is an even handier method.
img = cv2.imread('Images/3.jpg')img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)img = cv2.transpose(img)fig, ax = plt.subplots(1, figsize=(12,8))ax.axis('off')   plt.imshow(img)
We can create a mirror effect, like a reflection. We just need .flip with the image and the direction we want to reflect.
img = cv2.imread('Images/2.jpeg')img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)fig, ax = plt.subplots(1, figsize=(12,12))ax = plt.subplot(221) plt.imshow(img)img2 = cv2.flip(img, 1)ax = plt.subplot(222) plt.imshow(img2)img3 = cv2.flip(img, 0)ax = plt.subplot(223) plt.imshow(img3)img4 = cv2.flip(img, -1)ax = plt.subplot(224) plt.imshow(img4)
Many times we may need a specific part of the image instead of the whole thing. If that’s the case, we can easily crop the picture by slicing the NumPy array.
The first part of the slice would be related to the height, so it’s like row numbers if we think of it as a table, the second part refers to the width, so it’s like column numbers.
img = cv2.imread('Images/2.jpeg')img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)fig, ax = plt.subplots(1, figsize=(12,8))ax.axis('off')# [height start: height end, width start: width end]# [first row : last row, first column: last column]plt.imshow(img[500:1000, 250:500])
It’s not optimal to guess which pixel we want to start and end the crop, so let’s build a simple scaling function to understand how would we do this systematically.
# scale functiondef px(size, proportion):    return int(size * proportion)# read, convert and get height/ widthimg = cv2.imread('Images/2.jpeg')img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)h, w = img.shape[:2]# cropimg = img[px(h, .25):px(h, .40), #25% to 40%          px(w, .10):px(w, .35)] #10% to 35%# plotfig, ax = plt.subplots(1, figsize=(12,8))ax.axis('off')   plt.imshow(img)
By default, resizing is achieved with linear interpolations, but we can also try other methods to see the difference between them.
Let’s try using .resize. We’ll need the image and the desired size, and here we can specify the interpolation method we want. The size can be the dimensions of the new photo or a scaling factor for x and y.
# read, convert and get height/ widthimg = cv2.imread('Images/2.jpeg')img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)h, w = img.shape[:2]# cropimg = img[px(h, .33):px(h, .35),           px(w, .18):px(w, .25)]# figurefig, ax = plt.subplots(1, figsize=(12,16))# resize - nearestax = plt.subplot(311)img_scaled = cv2.resize(img, None, fx=3, fy=3, interpolation = cv2.INTER_NEAREST)plt.imshow(img_scaled)plt.title('nearest')# resize - default/ linearax = plt.subplot(312)img_scaled = cv2.resize(img, None, fx=3, fy=3)plt.imshow(img_scaled)plt.title('default/ linear')# resize - cubicax = plt.subplot(313)img_scaled = cv2.resize(img, None, fx=3, fy=3, interpolation = cv2.INTER_CUBIC)plt.imshow(img_scaled)plt.title('cubic')
Another way of resizing is by using pyramids. Let’s have a quick look at how does this works.
# read and convertimg = cv2.imread('Images/ufo.jpg')img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# cropimg = img[50:150, 200:400]# figurefig, ax = plt.subplots(1, figsize=(12,12))# imgax = plt.subplot(311)plt.imshow(img)plt.title('regular size')# downsizesmall = cv2.pyrDown(img)ax = plt.subplot(312)plt.imshow(small)plt.title('smaller')# upsizeregular = cv2.pyrUp(small)ax = plt.subplot(313)plt.imshow(regular)plt.title('regular again')
We can see that just like the others, there’s some loss in resolution, but overall it’s an excellent method.
Ok, just one more before we finish.
Slicing our array is not just useful for cropping, we can also use it to manipulate a specific part of the image, and even replace a slice for something different.
# read and convertimg = cv2.imread('Images/ufo.jpg')img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)img2 = cv2.imread('Images/8.jpg')img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)# replaceimg2[200:250, 1000:1100] = small# plotfig, ax = plt.subplots(1, figsize=(12,12))plt.imshow(img2)
Alright, we saw how to move, rotate, and resize an image. We also got a look at cropping, mirroring, and how to manipulate slices of a picture.
Thanks for reading my article. I hope you enjoyed it.
References:OpenCV Geometric Image Transformations;OpenCV Operations on arrays;OpenCV Transpose Example;OpenCV Pyramids;