You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确修改图像饱和度与明度通道?解决Python肤色调整异常

Fixing HSV Saturation/Value Adjustment Artifacts in Python

Let's break down exactly why you're seeing those weird black/red pixels and red overflow, then fix it with practical, artifact-free solutions.

Root Cause of the Issues

Your current code modifies sat and val directly as 8-bit unsigned integers (uint8) without restricting values to the valid [0, 255] range. Here's what's happening:

  • When you do sat[sat>0]+=60, any saturation value above 195 will exceed 255. Since uint8 wraps around (e.g., 250 + 10 = 4), this creates garbage pixel values like red overflow.
  • val[val<255]-=100 makes values below 100 wrap to negative-equivalent uint8 values (e.g., 50 - 100 = 206), causing those black outline artifacts.

Solution 1: Clamp Values to Valid Range

First, convert your HSV channels to a higher-bit integer type (like int32) before modifying them, then clamp results back to [0, 255] and convert back to uint8. This eliminates overflow entirely.

Here's the fixed core adjustment code:

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

for img in list_im:
    ori_image = cv2.imread(img)
    # Convert to HSV_FULL (H mapped 0-360 → 0-255; S/V 0-255)
    hsv_image = cv2.cvtColor(ori_image, cv2.COLOR_BGR2HSV_FULL)
    
    # Split channels and upgrade to int32 to avoid overflow
    h = hsv_image[:, :, 0].astype(np.int32)
    sat = hsv_image[:, :, 1].astype(np.int32)
    val = hsv_image[:, :, 2].astype(np.int32)
    
    # Adjust with clamping (choose one option)
    # Option A: Fixed offset (your original intent)
    sat = np.clip(sat + 60, 0, 255)  # Lock to 0-255
    val = np.clip(val - 100, 0, 255)
    
    # Option B: Proportional adjustment (more natural across images)
    # sat = np.clip(sat * 1.6, 0, 255)  ~60% saturation boost
    # val = np.clip(val * 0.6, 0, 255)  ~40% brightness reduction
    
    # Merge channels and convert back to uint8
    adjusted_hsv = np.dstack((h, sat, val)).astype(np.uint8)
    im = cv2.cvtColor(adjusted_hsv, cv2.COLOR_HSV2RGB_FULL)
    
    # Your existing plotting code (minor cleanup)
    fig = plt.figure(figsize=(18.5, 10.5))
    ax1 = fig.add_subplot(5, 1, 1)
    ax1.imshow(cv2.cvtColor(ori_image, cv2.COLOR_BGR2RGB))
    ax1.set_title('Original')
    
    ax2 = fig.add_subplot(5, 1, 2)
    ax2.imshow(im)
    ax2.set_title('Artifact-Free Adjustment')
    
    plt.subplots_adjust(top=2)
    save_path = os.path.join('/home/cgal/color_harmonization/', 'Result_saturation', os.path.basename(img))
    plt.savefig(save_path, bbox_inches="tight")
    plt.show()

Solution 2: Apply Adjustments Only to Masked Areas

Your mask function is great for excluding eyes/mouth, but you need to ensure adjustments only affect the jawline region. Here's how to integrate the mask properly:

First, update your mask function to return a 3-channel mask (matches color image dimensions):

from PIL import Image, ImageDraw

def getmask(img, jawline, eyes, mouth):
    height, width = img.shape[:2]
    # Create mask (white = apply adjustment; black = exclude)
    mask_im = Image.new('L', (width, height), 0)
    draw = ImageDraw.Draw(mask_im)
    
    # Draw jawline (target area)
    jawline_poly = jawline.flatten().tolist()
    draw.polygon(jawline_poly, fill='white')
    
    # Draw eyes (exclude)
    right_eyes = eyes[0:6].flatten().tolist()
    draw.polygon(right_eyes, fill='black')
    left_eyes = eyes[6:].flatten().tolist()
    draw.polygon(left_eyes, fill='black')
    
    # Draw mouth (exclude)
    mouth_poly = mouth.flatten().tolist()
    draw.polygon(mouth_poly, fill='black')
    
    # Convert to 3-channel normalized mask (0-1 range)
    mask = np.array(mask_im)
    mask_3ch = np.repeat(mask[:, :, np.newaxis], 3, axis=2) / 255.0
    return mask_3ch

Then, integrate it into your main workflow to apply adjustments selectively:

for img in list_im:
    ori_image = cv2.imread(img)
    ori_rgb = cv2.cvtColor(ori_image, cv2.COLOR_BGR2RGB)
    hsv_image = cv2.cvtColor(ori_image, cv2.COLOR_BGR2HSV_FULL)
    
    # Get mask (replace jawline/eyes/mouth with your actual landmark data)
    mask_3ch = getmask(ori_image, jawline, eyes, mouth)
    
    # Adjust HSV channels with clamping
    h = hsv_image[:, :, 0].astype(np.int32)
    sat = hsv_image[:, :, 1].astype(np.int32)
    val = hsv_image[:, :, 2].astype(np.int32)
    
    sat_adjusted = np.clip(sat + 60, 0, 255)
    val_adjusted = np.clip(val - 100, 0, 255)
    
    # Convert back to RGB and apply mask
    adjusted_hsv = np.dstack((h, sat_adjusted, val_adjusted)).astype(np.uint8)
    adjusted_rgb = cv2.cvtColor(adjusted_hsv, cv2.COLOR_HSV2RGB_FULL)
    
    # Blend original and adjusted image using mask
    final_image = (ori_rgb * (1 - mask_3ch) + adjusted_rgb * mask_3ch).astype(np.uint8)
    
    # Plotting code
    fig = plt.figure(figsize=(18.5, 10.5))
    ax1 = fig.add_subplot(5, 1, 1)
    ax1.imshow(ori_rgb)
    ax1.set_title('Original')
    
    ax2 = fig.add_subplot(5, 1, 2)
    ax2.imshow(final_image)
    ax2.set_title('Masked Artifact-Free Adjustment')
    
    plt.subplots_adjust(top=2)
    save_path = os.path.join('/home/cgal/color_harmonization/', 'Result_saturation', os.path.basename(img))
    plt.savefig(save_path, bbox_inches="tight")
    plt.show()

Key Takeaways

  • Always clamp values: 8-bit image channels can never go outside [0,255]. Using np.clip or upgrading to a higher-bit type first eliminates overflow artifacts.
  • Proportional adjustments are better: Multiplying saturation/value by a factor (e.g., sat * 1.6) gives more consistent, natural results across different images than fixed offsets.
  • Align masks correctly: Ensure your mask matches the image dimensions, and use 3-channel masks for color images to avoid channel mismatches.

内容的提问来源于stack exchange,提问作者Wanttobepro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:17:54