如何用Python和Pillow自定义权重将彩色图转灰度图
Great question! I’ve dealt with exactly this when simulating film photography filters in PIL—here’s how you can implement custom RGB weightings to get those classic black-and-white filter effects.
First, a quick recap: PIL’s built-in convert('L') uses the standard ITU-R 601-2 formula:
L = R * 299/1000 + G * 587/1000 + B * 114/1000
But this doesn’t let you tweak weights for artistic filters. Instead, we can manually compute the grayscale values using our own custom weights.
Method 1: Using ImageMath.eval (Simple & PIL-Native)
This approach keeps everything within PIL’s ecosystem, no extra libraries needed. We’ll split the image into RGB channels, apply our weight formula, and merge back into a grayscale image.
from PIL import Image, ImageMath def custom_filter_grayscale(image, r_weight, g_weight, b_weight): # Split the input image into its red, green, blue channels r_channel, g_channel, b_channel = image.split() # Calculate custom luminance using our weights custom_gray = ImageMath.eval( "r * rw + g * gw + b * bw", r=r_channel, g=g_channel, b=b_channel, rw=r_weight, gw=g_weight, bw=b_weight ) # Convert the math result back to a PIL grayscale image (mode "L") return Image.merge("L", [custom_gray]) # Example 1: Red filter (only red contributes to brightness) original_img = Image.open("your_photo.jpg") red_filter_result = custom_filter_grayscale(original_img, 1.0, 0.0, 0.0) red_filter_result.save("red_filter_bw.jpg") # Example 2: Orange filter (mix red and green equally) orange_filter_result = custom_filter_grayscale(original_img, 0.5, 0.5, 0.0) orange_filter_result.save("orange_filter_bw.jpg")
Method 2: Using NumPy (Flexible for Larger Images/Complex Logic)
If you’re working with larger images or want to add extra processing (like contrast adjustments), using NumPy gives you more flexibility. It converts the image to an array, applies the weight formula, and converts back to a PIL image.
from PIL import Image import numpy as np def custom_filter_grayscale_np(image, r_weight, g_weight, b_weight): # Convert PIL image to a NumPy array (shape: [height, width, 3] for RGB) img_array = np.array(image) # Apply our custom weighting formula across all pixels gray_array = ( img_array[..., 0] * r_weight + # Red channel img_array[..., 1] * g_weight + # Green channel img_array[..., 2] * b_weight # Blue channel ) # Ensure pixel values stay within the valid 0-255 range and convert to 8-bit integers gray_array = np.clip(gray_array, 0, 255).astype(np.uint8) # Convert back to a PIL grayscale image return Image.fromarray(gray_array, mode="L") # Usage is identical to the PIL-native method original_img = Image.open("your_photo.jpg") orange_filter_result = custom_filter_grayscale_np(original_img, 0.5, 0.5, 0.0) orange_filter_result.save("orange_filter_bw_np.jpg")
Quick Notes:
- Weight Sums: You don’t have to make your weights add up to 1, but if their sum exceeds 1, use
np.clip(or manually clamp values inImageMath) to avoid over-bright pixels that get clipped unexpectedly. - Filter Accuracy: These methods mimic real glass filters perfectly—red filters will make red objects appear brighter and blue objects darker, just like shooting with a red filter on film.
内容的提问来源于stack exchange,提问作者Touko

