Practical examples of image transformations with OpenCV (3x3 matrices and tomography) in Streamlit

few hours with…
6 min readDec 2, 2023

Recently I had a task that required some more advanced knowledge about image transformations. I started my research with simple image transformations and decided to create some hands-on examples using my favorite visualization tool — Streamlit. So first I went through simple transformations such as scaling, skewing, moving, etc.

For this tutorial, I will be using different 3x3 matrices to transform images and the OpenCV function warpPerspective. I will not go deep in theory as today we have tools like “chatGPT” where you can ask specific questions, but I will have a more practical approach. I have created a Streamlit app with examples so let's explore them.

In the beginning, navigate to opencv.streamlit.app/ and you will see the following screen:

Click on the selection bar and choose “3x3 transformations” for the first part:

Next, you can add your image or just use the demo image:

The first function is just image resizing, so if you have a large image but a small screen you can reduce image size and see it better.

For the first real function let's see scaling. We need to 3x3 matrix, for this, I will use numpy and create “np.array” and OpenCV's “cv2.warpPerspective” to scale the image.

scaling_matrix = np.array([
[scale_x, 0, 0],
[0, scale_y, 0],
[0, 0, 1]
], dtype=np.float32)

scaled_image = cv2.warpPerspective(image, scaling_matrix, (int(image.shape[1] * scale_x), int(image.shape[0] * scale_y)))

The scaling matrix requires scale for the horizontal direction (x) and the vertical direction (y). If you leave both values as 1 then the image stays as it is, because then it would be just an identity matrix. When using an identity matrix, the image will not transform. In Streamlit, I created so you can change scales from 1 to 3 and you can use sliders to play with values. For your project, you can change values as necessary.

The next will be skew. For skew, we need to give a value in radians and then calculate the tangent of that value. We will also need to calculate new image dimensions.

skew_radians = np.radians(skew)

skew_matrix = np.array([
[1, np.tan(skew_radians), 0],
[0, 1, 0],
[0, 0, 1]
], dtype=np.float32)2)

original_width, original_height = image.shape[1], image.shape[0]
new_width = int(original_width * np.cos(skew_radians) + original_height * np.abs(np.sin(skew_radians)))
new_height = original_height

skewed_image = cv2.warpPerspective(image.copy(), skew_matrix, (new_width, new_height))

Same as previously you can play with skew value with slider.

Next in line in the translation matrix. Here we need pixel values for how much we will move the image horizontally (tx) and vertically (ty).

translation_matrix = np.array([
[1, 0, tx],
[0, 1, ty],
[0, 0, 1]
], dtype=np.float32)

translated_image = cv2.warpPerspective(image.copy(), translation_matrix, (image.shape[1], image.shape[0]))

In the Streamlit app, you can use a slider to move the image horizontally and vertically.

The last simple transformation is rotation. Rotation is a bit trickier and requires more calculations than previous transformations. To not overcomplicate we will rotate the image along the image center, so firstly we need to get the center of the image (center_x and center_y). The image in the example is turned 45 degrees, we need to change the value to radians (angle_radians).

x1 = np.cos(angle_radians)
x2 = -np.sin(angle_radians)
x3 = (1 - np.cos(angle_radians)) * center_x + np.sin(angle_radians) * center_y]
y1 = np.sin(angle_radians)
y2 = np.cos(angle_radians)
y3 = -np.sin(angle_radians) * center_x + (1 - np.cos(angle_radians)) * center_y]

rotation_matrix = np.array([[x1, x2, x3,
[y1, y2, y3,
[0, 0, 1]], dtype=np.float32)

In the Streamlit app, you can again use the slider to change the rotation angle from -90 till 90 degrees.

It is quite cool what we can do with just a few lines of code, but the cool part is that we can use these matrices in combinations. To do that we can just simply do matrix multiplication and pass the final value to “cv2.warpPerspective”.

composite_matrix = np.dot(skewing_matrix, np.dot(scaling_matrix, np.dot(rotation_matrix, translation_matrix)))
transformed_image = cv2.warpPerspective(image.copy(), composite_matrix, (image.shape[1], image.shape[0]))

On the Streamlit app also created a combined example with all previous sliders.

One cool example where we can use this is homography (a projective transformation). We can take four points on the image and move them to the other four points. I created a simple example for this also on the Streamlit app. Let's take an image of a wall painting and we will put it in a room wall frame, so if we would like to see how it fits our room before we buy it. As a painting, I will take a wall poster from an Etsy shop, you can see it here and as a room image, I found this image on Unsplash.

Let's navigate to “Homography” in the selection bar on Streamlit. As previously there are also demo images but let's use the images I prepared. We will put the image on the left in one of the frames in the image on the right:

First, we need to choose four points in the foreground image which will be transferred to the second image. For this, you can use four sliders to control four points. The image is very colorful but you can see red dots in corners. If you wanna transfer the whole image then you can use all coordinates 0, but to better see I shifted 10 pixels from all sides:

Similarly, now we need to navigate four points into the frame. Navigations for horizontal movement are straightforward but up and down is not the best solution, but for this example code it's probably ok. When all four points are inside the frame then you can scroll down and see the result.

Added also download button if you need to save the image. The final result for this example looks like this:

That's about it for this tutorial. This is very basic stuff but I have already dug deeper into more interesting concepts, I just need just to find time to put them into Streamlit for better visualizations. The full code is on Git Hub.

--

--

few hours with…

Writing a blog about learning and exploring new stuff just for fun.