Files
orisys-sdk/patch_generation/test_hue_gray.py
T
2026-01-08 14:23:38 +08:00

225 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import cv2
import numpy as np
import matplotlib.pyplot as plt
def calculate_local_contrast(image_input, window_size=5):
"""
Calculate Global Average Local Contrast (for fine texture detail evaluation in optical flow).
Args:
image_input: str (image file path) or numpy array (grayscale/RGB image).
window_size: int (odd number, default=5) — sliding window size for fine texture analysis.
Returns:
float: Global average local contrast (higher = better fine texture detail; target ≥0.2 for optical flow).
Raises:
ValueError: If image is invalid, window size is even/too small, or processing fails.
"""
# ---------------------- Step 1: Input Validation & Preprocessing ----------------------
# Validate window size (must be odd, ≥3 to capture meaningful texture)
if window_size % 2 == 0 or window_size < 3:
raise ValueError(f"Window size must be an odd integer ≥3 (got {window_size}). Use 3/5/7.")
# Load/convert image to grayscale (consistent with optical flow workflow)
if isinstance(image_input, str):
img = cv2.imread(image_input)
if img is None:
raise ValueError(f"Failed to load image from path: {image_input}")
elif isinstance(image_input, np.ndarray):
img = image_input.copy()
else:
raise ValueError("Input must be an image file path (str) or numpy array (image).")
# Convert to grayscale (handle RGB/BGR)
if len(img.shape) == 3:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
else:
gray = img # Already grayscale
# Normalize to 0-255 (avoid non-standard pixel ranges, e.g., float32)
gray = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
# ---------------------- Step 2: Sliding Window Local Contrast Calculation ----------------------
# Define padding (replicate edge pixels to avoid boundary distortion)
pad_size = window_size // 2
gray_padded = cv2.copyMakeBorder(
gray,
top=pad_size, bottom=pad_size, left=pad_size, right=pad_size,
borderType=cv2.BORDER_REPLICATE
)
# Initialize array to store local contrast for each window
local_contrast_vals = []
# Iterate over every pixel (center of the sliding window)
for y in range(pad_size, gray_padded.shape[0] - pad_size):
for x in range(pad_size, gray_padded.shape[1] - pad_size):
# Extract 5x5 (or custom) window around (x,y)
window = gray_padded[y-pad_size : y+pad_size+1, x-pad_size : x+pad_size+1]
# Compute Michelson contrast for the window (avoid division by zero)
window_max = np.max(window)
window_min = np.min(window)
if window_max + window_min == 0: # All pixels are 0 (black window)
lc = 0.0
else:
lc = (window_max - window_min) / (window_max + window_min)
local_contrast_vals.append(lc)
# ---------------------- Step 3: Global Average Local Contrast ----------------------
global_local_contrast = np.mean(local_contrast_vals) if local_contrast_vals else 0.0
# Round for readability (4 decimal places)
return global_local_contrast
def calculate_edge_strength_density(image_input):
"""
Calculate Edge Strength and Edge Density for optical flow evaluation.
Args:
image_input: Can be (1) str (image file path) or (2) numpy array (grayscale/RGB image).
Returns:
dict: Contains two metrics:
- edge_strength: Average gradient magnitude of valid edges (higher = sharper edges).
- edge_density: Percentage of valid edge pixels (higher = more texture/edges).
Raises:
ValueError: If image cannot be loaded/processed.
"""
# ---------------------- Step 1: Load/Preprocess Image ----------------------
img = image_input
# Convert to grayscale (handle RGB/BGR)
if len(img.shape) == 3:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
else:
gray = img # Already grayscale
# Normalize to 0-255 (avoid float/non-standard pixel ranges)
gray = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
# ---------------------- Step 2: Compute Gradient Magnitude (Sobel) ----------------------
# Sobel kernel size = 3 (balance between noise reduction and edge sharpness for optical flow)
sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) # Horizontal gradient
sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) # Vertical gradient
# Compute gradient magnitude (edge strength raw values)
gradient_magnitude = np.sqrt(sobel_x**2 + sobel_y**2)
# ---------------------- Step 3: Adaptive Threshold for Valid Edges ----------------------
# Use median gradient to filter noise (robust to outliers)
median_gradient = np.median(gradient_magnitude)
# Threshold = 1.5× median (filters weak noise, keeps meaningful edges for optical flow)
edge_threshold = 1.5 * median_gradient
# Create binary mask for valid edges (1 = valid edge, 0 = non-edge)
valid_edge_mask = (gradient_magnitude > edge_threshold).astype(np.uint8)
# ---------------------- Step 4: Calculate Metrics ----------------------
# Edge Strength: Average gradient magnitude of valid edges
valid_gradient_values = gradient_magnitude[valid_edge_mask == 1]
edge_strength = np.mean(valid_gradient_values) if len(valid_gradient_values) > 0 else 0.0
# Edge Density: Percentage of valid edge pixels (total pixels = height × width)
total_pixels = gray.shape[0] * gray.shape[1]
valid_edge_pixels = np.sum(valid_edge_mask)
edge_density = (valid_edge_pixels / total_pixels) * 100 if total_pixels > 0 else 0.0
# print(f"edge_strength: {edge_strength}, edge_density: {edge_density}")
# Round for readability (3 decimal places)
return edge_strength, edge_density
def custom_gray_conversion(img, r_weight=0.299, g_weight=0.587, b_weight=0.114,
red_boost=1.2, green_reduce=0.8):
"""
Custom grayscale calculation with tunable RGB weights + red/green contrast enhancement
Args:
image_path: Path to input image
r_weight: Weight for Red channel (default: 0.299)
g_weight: Weight for Green channel (default: 0.587)
b_weight: Weight for Blue channel (default: 0.114)
red_boost: Multiplier to boost red region grayscale (>1 = brighter)
green_reduce: Multiplier to reduce green region grayscale (<1 = darker)
Returns:
default_gray: Grayscale with standard weights
custom_gray: Grayscale with tunable weights + red/green enhancement
"""
# 1. Read image and validate
# img_bgr = cv2.imread(image_path)
img_bgr = img
if img_bgr is None:
raise ValueError("Failed to read image - check path!")
# 2. Convert BGR (OpenCV default) to RGB and split channels
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
# Convert to float32 to avoid integer overflow/precision loss
r = img_rgb[:, :, 0].astype(np.float32)
g = img_rgb[:, :, 1].astype(np.float32)
b = img_rgb[:, :, 2].astype(np.float32)
# 3. Calculate DEFAULT grayscale (standard weights)
default_gray = 0.299 * r + 0.587 * g + 0.114 * b
# 4. Calculate CUSTOM grayscale (tunable weights)
custom_gray_base = r_weight * r + g_weight * g + b_weight * b
# 5. Enhance red/green contrast (optional but effective)
# Define red/green regions using RGB value ratios (no HSV needed)
# red_mask = (r > g * 1.2) & (r > b * 1.2) & (r >= 40) # Red-dominant pixels
# green_mask = (g > r * 1.2) & (g > b * 1.2) & (g >= 40) # Green-dominant pixels
# Apply targeted adjustments to red/green regions
custom_gray = custom_gray_base.copy()
# custom_gray[red_mask] *= red_boost # Boost red region grayscale
# custom_gray[green_mask] *= green_reduce # Reduce green region grayscale
# 6. Normalize to 0-255 range and convert back to uint8 (OpenCV standard)
# Clip values first to avoid overflow, then scale if needed
default_gray = np.clip(default_gray, 0, 255).astype(np.uint8)
custom_gray = np.clip(custom_gray, 0, 255).astype(np.uint8)
return default_gray, custom_gray
# -------------------------- Test the Code --------------------------
if __name__ == "__main__":
# Replace with your image path
IMAGE_PATH = "./data/new_texture.jpg"
img = cv2.imread(IMAGE_PATH)
dx = -80
dy = 0
tile = img[410+dy:650+dy,940+dx:1180+dx,:]
# Example 1: Use standard weights (verify correctness)
default_gray, red_gray = custom_gray_conversion(
tile,
r_weight=1, g_weight=0, b_weight=0
)
default_gray, green_gray = custom_gray_conversion(
tile,
r_weight=0, g_weight=1, b_weight=0
)
default_gray, blue_gray = custom_gray_conversion(
tile,
r_weight=0, g_weight=0, b_weight=1
)
edge_strength, edge_density = calculate_edge_strength_density(default_gray)
local_contrast = calculate_local_contrast(default_gray)
print(f"标准灰度: 边缘强度: {edge_strength}, 边缘密度: {edge_density}, 局部对比度: {local_contrast}")
edge_strength, edge_density = calculate_edge_strength_density(red_gray)
local_contrast = calculate_local_contrast(red_gray)
print(f"红色灰度: 边缘强度: {edge_strength}, 边缘密度: {edge_density}, 局部对比度: {local_contrast}")
edge_strength, edge_density = calculate_edge_strength_density(green_gray)
local_contrast = calculate_local_contrast(green_gray)
print(f"绿色灰度: 边缘强度: {edge_strength}, 边缘密度: {edge_density}, 局部对比度: {local_contrast}")
edge_strength, edge_density = calculate_edge_strength_density(blue_gray)
local_contrast = calculate_local_contrast(blue_gray)
print(f"蓝色灰度: 边缘强度: {edge_strength}, 边缘密度: {edge_density}, 局部对比度: {local_contrast}")
plt.subplot(2, 2, 1)
plt.imshow(default_gray, cmap='gray')
plt.subplot(2, 2, 2)
plt.imshow(red_gray, cmap='gray')
plt.subplot(2, 2, 3)
plt.imshow(green_gray, cmap='gray')
plt.subplot(2, 2, 4)
plt.imshow(blue_gray, cmap='gray')
plt.show()