[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky] [Facebook]
[Build Your Own Python Action Arcade!]

[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Use a color matrix to remove red, green, and blue color components from images in Python

[A bird image with the green color component removed in Python]

My previous post Use point operations to make grayscale images in Python showed how to change the red, green, and blue color components of pixels to convert an image into grayscale. It also showed how you can modify pixels using a hard-coded equation to combine color coordinates to produce sepia tone. This example generalizes that technique to let you apply a matrix to a pixel's color components. That lets you create sepia tone, remove particular components from an image, and much more.

Color Matrices

The previous example uses the following equations to convert a pixel's to sepia tone.

new_r = round(r * 0.393 + g * 0.769 + b * 0.189) new_g = round(r * 0.349 + g * 0.686 + b * 0.168) new_b = round(r * 0.272 + g * 0.534 + b * 0.131)

That works but it isn't very flexible. This example lets you multiply a pixel's color components by a matrix. That lets you set each new color component to an arbitrary combination of the old components' values.

To make the method even more flexible, we'll use homogeneous color coordinates that include an extra scaling factor at the end so each pixel has four coordinates: red, green, blue, and a scale factor (which is usually 1). (If you're also using the alpha component, use five values so you can add the scale factor at the end.)

Now you multiply a pixel's four color components by a 4×4 matrix. For example, here's the matrix for sepia tone.

0.393, 0.769, 0.189, 0 0.349, 0.686, 0.168, 0 0.272, 0.534, 0.131, 0 0.000, 0.000, 0.000, 1

When you multiply this matrix by the vector [r, g, b, 1], you get the same results we got in the previous example.

Sepia Tone Revisited

The following code shows the new apply_matrix method that applies a color transformation matrix to an image's pixels.

@staticmethod def apply_matrix(image, matrix): '''Apply the matrix to each point.''' def matrix_times_vector(m, v): '''Multiply the 4x4 matrix by the 1x4 vector.''' return ( round(m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2] + m[0][3] * v[3]), round(m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2] + m[1][3] * v[3]), round(m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2] + m[2][3] * v[3]), round(m[3][0] * v[0] + m[3][1] * v[1] + m[3][2] * v[2] + m[3][3] * v[3]), ) # A comprehension is slower. # return [sum([v[i] * m[j][i] for i in range(4)]) for j in range(4)] # Make a copy and get its pixels. result_image = image.copy() result_pixels = result_image.load() # process the pixels. for y in range(image.height): for x in range(image.width): # Apply the matrix to pixel[x, y]. pixel = result_pixels[x, y] + (1,) result_pixels[x, y] = tuple( matrix_times_vector(matrix, pixel)[:3]) # Return the result. return result_image

The method first defines the nested matrix_times_vector function that multiples a matrix by a vector. It then copies the image, loads the copy's pixels, and loops through the pixels applying the matrix to each.

For each pixel, the code gets the pixel's red, green, and blue values. It then appends the value 1 to add the point's scale factor. It then calls the matrix_times_vector function to apply the matrix to the vector.

The result is a new vector with four components and we only want the first three, so the code uses list slicing to pull out the first three. It converts the result into a tuple and saves the result in result_pixels[x, y]. (That value must be a tuple or number, not a list of color components.)

The following code shows how the new example converts an image to sepia tone.

@staticmethod def to_sepia_tone(image): # Build a color matrix. matrix = [ [0.393, 0.769, 0.189, 0], [0.349, 0.686, 0.168, 0], [0.272, 0.534, 0.131, 0], [0.000, 0.000, 0.000, 1] ] return PointOps.apply_matrix(image, matrix)

Speed Issues

The program must multiply the color matrix by each pixel's color components for every pixel in the image. For large images, that adds up to a lot of operations, so it's worth taking a little time to experiment with the best approach.

Online people generally recommend using the numpy library to multiply a matrix by a vector. That works but it requires you to convert the matrix into a numpy matrix. That only happens once so it's not a big deal, but you also need to convert each pixel's color components into a numpy vector and that adds up when you do it for every pixel.

Another approach would be to use a list comprehension to multiply the matrix by the vector.

Finally, you can take the approach used by the previous code which performs the multiplications and additions all written out. That's more verbose but the operations are extremely simple and don't require the program to convert into numpy objects or to write code to implement a list comprehension.

The following table shows hoe those methods compared in one set of tests.

ApproachTime
Previous example
(no matrix)
0.198866 seconds
This example
(operations written out)
0.265215 seconds
List comprehension0.347089 seconds
numpy1.100865 seconds

The previous example was the fastest but it doesn't provide the flexibility that a matrix does. The code shown above, which performs the multiplications and additions all written out explicitly, is the next fastest. The list comprehension takes slightly longer and numpy takes significantly longer.

All this is why the current example performs is operations all written out.

There's one more operation that people sometimes implement. After multiplying the matrix by the color component vector, the result vector may have a scale factor that isn't 1. In that case, you can normalize the result vector by dividing all of the color components by that scale factor. That isn't necessary if the vector's scale factor is 1 and the final column in the matrix holds the values 0, 0, 0, and 1, so I'm skipping this to save a tiny amount of time.

Other Menu Commands

[The example program provides these Color commands] The program's Color menu contains the commands shown in the picture on the right. The Average and Grayscale commands are the same as in the previous example, and you saw the Sepia Tone command's code earlier. The other commands use the following pattern.

color_menu.add_command(label='No Blue', command=lambda: self.mnu_scale_colors(1, 1, 0)) color_menu.add_command(label='Only Red/Green', command=lambda: self.mnu_scale_colors(1, 1, 0))

Each of these calls the mnu_scale_colors method passing it scale factors for the red, green, and blue color components. For example, the No Blue command passes 1 for the red and green components and 0 for the blue component.

The following code shows the mnu_scale_colors method.

def mnu_scale_colors(self, r_scale, g_scale, b_scale): matrix = [ [r_scale, 0, 0, 0], [ 0, g_scale, 0, 0], [ 0, 0, b_scale, 0], [ 0, 0, 0, 1], ] self.pil_image = PointOps.apply_matrix(self.pil_image, matrix) self.show_result()

This code uses the scale factors to create a color matrix. It then calls PointOps.apply_matrix to apply the matrix to the image and displays the result.

Take a look at the code to see how the others work.

Conclusion

A color matrix lets you easily apply all sorts of transformations to an image's colors. This example uses matrices to convert to sepia tone. It also uses matrices to knock out the image's red, green, or blue color components. For example, if you remove the green color component, the result uses only shades of red and blue.

In my next post, I'll show how you can use color matrices to make an image convert all of its colors to a specific color tone. For example, you can make an image use yellow, orange, or purple tones.

Meanwhile, download the example to experiment with it and to see additional details.

© 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.