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()