diff --git a/Lighting_and_Color.py b/Lighting_and_Color.py index 6491594..0dcdd58 100644 --- a/Lighting_and_Color.py +++ b/Lighting_and_Color.py @@ -24,7 +24,7 @@ def tensor2pil(tensor): def pil2tensor(image, original_shape): """ PIL画像をPyTorchテンソルに変換し、元の形状に合わせる。 - BHWC形式で値が[0, 1]の範囲のテンソルを返す。 + BHWC形状で値が[0, 1]の範囲のテンソルを返す。 """ # numpy配列に変換し、[0, 1]に正規化 img_array = np.array(image).astype(np.float32) / 255.0 @@ -52,37 +52,27 @@ def medianFilter(image, radius, num_samples, threshold): class FluxLightingAndColor: """ - FluxLightingAndColor - 画像の照明と色調を調整するためのノードクラス + FluxLightingAndColor - Enhanced Version + 画像の照明と色調を自動解析し調整するためのノードクラス - 主な機能: - - 彩度調整 - - 被写界深度(DoF)処理 - - 最適化された処理順序 - - デバッグ出力 + 新機能: + - 画像解析による自動パラメーター補完 + - 色温度分析 + - 輝度分布解析 + - 彩度解析 + - カスタマイズ可能な自動調整 """ @classmethod def INPUT_TYPES(s): """ 入力パラメータの定義 - required: 必須パラメータ - - image: 入力画像 - - black/mid/white_level: レベル調整用パラメータ - - red/green/blue_level: 各色チャンネルの強度 - - brightness: 明るさ - - saturation: 彩度 - - optional: オプションパラメータ - - depth: 深度マップ画像 - - dof_mode: 被写界深度エフェクトモード - - dof_radius: ぼかしの半径 - - dof_samples: サンプル数 - - debug_mode: デバッグ出力の有無 """ return { "required": { "image": ("IMAGE",), + "auto_analysis": ("BOOLEAN", {"default": True}), + "analysis_strength": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.1}), "black_level": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}), "mid_level": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), "white_level": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), @@ -97,25 +87,224 @@ class FluxLightingAndColor: "dof_mode": (["none", "mock", "gaussian", "box"],), "dof_radius": ("INT", {"default": 8, "min": 1, "max": 128, "step": 1}), "dof_samples": ("INT", {"default": 1, "min": 1, "max": 3, "step": 1}), + "auto_brightness": ("BOOLEAN", {"default": True}), + "auto_color_balance": ("BOOLEAN", {"default": True}), + "auto_saturation": ("BOOLEAN", {"default": True}), + "auto_contrast": ("BOOLEAN", {"default": True}), "debug_mode": ("BOOLEAN", {"default": False}), } } - RETURN_TYPES = ("IMAGE",) + RETURN_TYPES = ("IMAGE", "STRING") + RETURN_NAMES = ("image", "analysis_report") FUNCTION = "apply_lighting_and_color" CATEGORY = "image/adjustments" + def analyze_image_characteristics(self, img_array, debug=False): + """ + 画像の特性を解析し、最適な調整パラメーターを算出する + + Returns: + - dict: 解析結果と推奨パラメーター + """ + analysis = {} + + # 輝度分析 + luminance = 0.299 * img_array[:,:,0] + 0.587 * img_array[:,:,1] + 0.114 * img_array[:,:,2] + + # 基本統計 + analysis['mean_luminance'] = float(np.mean(luminance)) + analysis['std_luminance'] = float(np.std(luminance)) + analysis['min_luminance'] = float(np.min(luminance)) + analysis['max_luminance'] = float(np.max(luminance)) + + # ヒストグラム分析 + hist, bins = np.histogram(luminance.flatten(), bins=256, range=(0, 1)) + analysis['histogram'] = hist + + # 色温度分析(簡易版) + mean_r = float(np.mean(img_array[:,:,0])) + mean_g = float(np.mean(img_array[:,:,1])) + mean_b = float(np.mean(img_array[:,:,2])) + + analysis['mean_rgb'] = [mean_r, mean_g, mean_b] + + # 色温度推定 (青が強い = 冷たい、赤が強い = 暖かい) + if mean_b > mean_r: + analysis['color_temperature'] = 'cool' + analysis['temp_bias'] = mean_b - mean_r + else: + analysis['color_temperature'] = 'warm' + analysis['temp_bias'] = mean_r - mean_b + + # 彩度分析 + hsv_array = cv.cvtColor((img_array * 255).astype(np.uint8), cv.COLOR_RGB2HSV) + saturation_values = hsv_array[:,:,1] / 255.0 + analysis['mean_saturation'] = float(np.mean(saturation_values)) + analysis['std_saturation'] = float(np.std(saturation_values)) + + # コントラスト分析 + analysis['contrast_ratio'] = analysis['max_luminance'] - analysis['min_luminance'] + + # 推奨パラメーター計算 + recommendations = self.calculate_recommendations(analysis, debug) + analysis['recommendations'] = recommendations + + if debug: + print("\n=== Image Analysis Results ===") + print(f"Mean Luminance: {analysis['mean_luminance']:.3f}") + print(f"Luminance Range: {analysis['min_luminance']:.3f} - {analysis['max_luminance']:.3f}") + print(f"Color Temperature: {analysis['color_temperature']} (bias: {analysis['temp_bias']:.3f})") + print(f"Mean Saturation: {analysis['mean_saturation']:.3f}") + print(f"Contrast Ratio: {analysis['contrast_ratio']:.3f}") + print("Recommendations:", recommendations) + print("===============================\n") + + return analysis + + def calculate_recommendations(self, analysis, debug=False): + """ + 解析結果に基づいて推奨パラメーターを計算する + """ + recommendations = {} + + # 明るさ調整の推奨値 + target_luminance = 0.5 + current_luminance = analysis['mean_luminance'] + + if current_luminance < 0.3: # 暗い画像 + recommendations['brightness'] = 1.2 + (0.3 - current_luminance) * 2 + elif current_luminance > 0.7: # 明るい画像 + recommendations['brightness'] = 0.8 + (0.7 - current_luminance) * 0.5 + else: + recommendations['brightness'] = 1.0 + + # コントラスト調整 + if analysis['contrast_ratio'] < 0.5: # 低コントラスト + recommendations['black_level'] = max(0, analysis['min_luminance'] - 0.05) + recommendations['white_level'] = min(1, analysis['max_luminance'] + 0.05) + else: + recommendations['black_level'] = 0.0 + recommendations['white_level'] = 1.0 + + recommendations['mid_level'] = 0.5 + + # 色温度補正 + temp_bias = analysis['temp_bias'] + if analysis['color_temperature'] == 'cool' and temp_bias > 0.05: + # 冷たすぎる場合、赤を強化、青を減少 + recommendations['red_level'] = 1.0 + min(temp_bias * 2, 0.3) + recommendations['green_level'] = 1.0 + recommendations['blue_level'] = 1.0 - min(temp_bias * 1.5, 0.2) + elif analysis['color_temperature'] == 'warm' and temp_bias > 0.05: + # 暖かすぎる場合、青を強化、赤を減少 + recommendations['red_level'] = 1.0 - min(temp_bias * 1.5, 0.2) + recommendations['green_level'] = 1.0 + recommendations['blue_level'] = 1.0 + min(temp_bias * 2, 0.3) + else: + recommendations['red_level'] = 1.0 + recommendations['green_level'] = 1.0 + recommendations['blue_level'] = 1.0 + + # 彩度調整 + mean_sat = analysis['mean_saturation'] + if mean_sat < 0.4: # 彩度が低い + recommendations['saturation'] = 1.0 + (0.4 - mean_sat) * 2 + elif mean_sat > 0.8: # 彩度が高い + recommendations['saturation'] = 1.0 - (mean_sat - 0.8) * 1.5 + else: + recommendations['saturation'] = 1.0 + + # 値を適切な範囲にクリップ + recommendations['brightness'] = np.clip(recommendations['brightness'], 0.0, 2.0) + recommendations['saturation'] = np.clip(recommendations['saturation'], 0.0, 2.0) + recommendations['red_level'] = np.clip(recommendations['red_level'], 0.0, 2.0) + recommendations['green_level'] = np.clip(recommendations['green_level'], 0.0, 2.0) + recommendations['blue_level'] = np.clip(recommendations['blue_level'], 0.0, 2.0) + + return recommendations + + def blend_parameters(self, user_params, recommendations, strength, auto_flags, debug=False): + """ + ユーザー設定と推奨値をブレンドする + + Parameters: + - user_params: ユーザーが設定したパラメーター + - recommendations: 解析による推奨パラメーター + - strength: 自動調整の強度 (0.0-1.0) + - auto_flags: 各調整項目の自動有効フラグ + """ + blended = {} + + param_mapping = { + 'brightness': 'auto_brightness', + 'saturation': 'auto_saturation', + 'red_level': 'auto_color_balance', + 'green_level': 'auto_color_balance', + 'blue_level': 'auto_color_balance', + 'black_level': 'auto_contrast', + 'white_level': 'auto_contrast', + 'mid_level': 'auto_contrast' + } + + for param_name in ['brightness', 'saturation', 'red_level', 'green_level', 'blue_level', + 'black_level', 'white_level', 'mid_level']: + + user_value = user_params.get(param_name, 1.0) + recommended_value = recommendations.get(param_name, user_value) + auto_flag_name = param_mapping.get(param_name, 'auto_brightness') + + if auto_flags.get(auto_flag_name, True): + # 自動調整が有効な場合、ユーザー値と推奨値をブレンド + blended[param_name] = user_value * (1 - strength) + recommended_value * strength + else: + # 自動調整が無効な場合、ユーザー値をそのまま使用 + blended[param_name] = user_value + + if debug: + print("\n=== Parameter Blending ===") + for param_name in blended: + user_val = user_params.get(param_name, 1.0) + rec_val = recommendations.get(param_name, user_val) + final_val = blended[param_name] + auto_flag = param_mapping.get(param_name, 'auto_brightness') + enabled = auto_flags.get(auto_flag, True) + print(f"{param_name}: User={user_val:.3f}, Rec={rec_val:.3f}, Final={final_val:.3f} (Auto: {enabled})") + print("=========================\n") + + return blended + + def generate_analysis_report(self, analysis, blended_params, user_params): + """ + 解析結果のレポートを生成する + """ + report = [] + report.append("=== Image Analysis Report ===") + report.append(f"Mean Luminance: {analysis['mean_luminance']:.3f}") + report.append(f"Luminance Range: {analysis['min_luminance']:.3f} - {analysis['max_luminance']:.3f}") + report.append(f"Color Temperature: {analysis['color_temperature']} (bias: {analysis['temp_bias']:.3f})") + report.append(f"Mean Saturation: {analysis['mean_saturation']:.3f}") + report.append(f"Contrast Ratio: {analysis['contrast_ratio']:.3f}") + report.append("") + report.append("=== Applied Adjustments ===") + + for param_name in ['brightness', 'saturation', 'red_level', 'green_level', 'blue_level']: + user_val = user_params.get(param_name, 1.0) + final_val = blended_params.get(param_name, user_val) + change = final_val - user_val + if abs(change) > 0.01: + sign = "+" if change > 0 else "" + report.append(f"{param_name}: {user_val:.3f} → {final_val:.3f} ({sign}{change:.3f})") + else: + report.append(f"{param_name}: {final_val:.3f} (no change)") + + report.append("=============================") + + return "\n".join(report) + def apply_dof(self, img, depth_map=None, mode='none', radius=8, samples=1, debug=False): """ 被写界深度(DoF)エフェクトを適用する - - Parameters: - - img: 元画像 - - depth_map: 深度マップ - - mode: エフェクトモード (none/mock/gaussian/box) - - radius: ぼかしの半径 - - samples: サンプル数 - - debug: デバッグ出力フラグ """ if mode == 'none' or depth_map is None: if debug: @@ -158,117 +347,167 @@ class FluxLightingAndColor: def adjust_levels(self, img_array, black_level, mid_level, white_level, debug=False): """ 画像のレベル調整を行う - - Parameters: - - img_array: 画像配列 - - black_level: 黒レベル - - mid_level: 中間トーン - - white_level: 白レベル - - debug: デバッグ出力フラグ """ if debug: - print(f"Levels: Adjusting (black: {black_level}, mid: {mid_level}, white: {white_level})") + print(f"Levels: Adjusting (black: {black_level:.3f}, mid: {mid_level:.3f}, white: {white_level:.3f})") + # Apply level adjustments img_array = (img_array - black_level) / (white_level - black_level) img_array = np.clip(img_array, 0, 1) + + # Apply gamma correction based on mid level + if mid_level != 0.5: + gamma = np.log(0.5) / np.log(mid_level + 1e-8) # avoid division by zero + img_array = np.power(img_array, gamma) + return img_array def adjust_channels(self, img_array, red_level, green_level, blue_level, debug=False): """ RGB各チャンネルの強度を調整する - - Parameters: - - img_array: 画像配列 - - red_level: 赤チャンネルの強度 - - green_level: 緑チャンネルの強度 - - blue_level: 青チャンネルの強度 - - debug: デバッグ出力フラグ """ if debug: - print(f"Channels: Adjusting (R: {red_level}, G: {green_level}, B: {blue_level})") + print(f"Channels: Adjusting (R: {red_level:.3f}, G: {green_level:.3f}, B: {blue_level:.3f})") + # Split and adjust each channel img_array[:,:,0] = np.clip(img_array[:,:,0] * red_level, 0, 1) img_array[:,:,1] = np.clip(img_array[:,:,1] * green_level, 0, 1) img_array[:,:,2] = np.clip(img_array[:,:,2] * blue_level, 0, 1) return img_array - def apply_lighting_and_color(self, image, black_level, mid_level, white_level, + def apply_lighting_and_color(self, image, auto_analysis, analysis_strength, + black_level, mid_level, white_level, red_level, green_level, blue_level, brightness, saturation, depth=None, dof_mode="none", dof_radius=8, dof_samples=1, - debug_mode=False): + auto_brightness=True, auto_color_balance=True, + auto_saturation=True, auto_contrast=True, debug_mode=False): """ - メインの処理関数。以下の順序で画像処理を実行: - 1. 入力テンソルをPIL画像に変換 - 2. 被写界深度エフェクトの適用(深度マップがある場合) - 3. numpy配列に変換 - 4. 明るさの調整 - 5. レベル調整 - 6. チャンネル調整 - 7. PIL画像に再変換 - 8. 彩度の調整 - 9. 最終的なコントラスト調整 - 10. テンソルに再変換して返却 + メインの処理関数。画像解析と自動調整機能を含む。 """ try: if debug_mode: - print("\n=== FluxLightingAndColor Starting ===") + print("\n=== FluxLightingAndColor Enhanced Starting ===") print(f"Input tensor shape: {image.shape}, dtype: {image.dtype}") + print(f"Auto analysis: {auto_analysis}, Strength: {analysis_strength}") # 1. 入力テンソルをPIL画像に変換 img_pil = tensor2pil(image) if debug_mode: print("Step 1: Converted input tensor to PIL image") - # 2. 深度マップがある場合はDoFを適用 + # 2. 画像解析(自動調整が有効な場合) + analysis_report = "No analysis performed" + final_params = { + 'black_level': black_level, + 'mid_level': mid_level, + 'white_level': white_level, + 'red_level': red_level, + 'green_level': green_level, + 'blue_level': blue_level, + 'brightness': brightness, + 'saturation': saturation + } + + if auto_analysis: + img_array_for_analysis = np.array(img_pil).astype(float) / 255.0 + analysis = self.analyze_image_characteristics(img_array_for_analysis, debug_mode) + + user_params = { + 'black_level': black_level, + 'mid_level': mid_level, + 'white_level': white_level, + 'red_level': red_level, + 'green_level': green_level, + 'blue_level': blue_level, + 'brightness': brightness, + 'saturation': saturation + } + + auto_flags = { + 'auto_brightness': auto_brightness, + 'auto_color_balance': auto_color_balance, + 'auto_saturation': auto_saturation, + 'auto_contrast': auto_contrast + } + + final_params = self.blend_parameters( + user_params, analysis['recommendations'], + analysis_strength, auto_flags, debug_mode + ) + + analysis_report = self.generate_analysis_report(analysis, final_params, user_params) + + if debug_mode: + print("Step 2: Completed image analysis and parameter blending") + + # 3. 深度マップがある場合はDoFを適用 if depth is not None and dof_mode != "none": depth_pil = tensor2pil(depth) img_pil = self.apply_dof(img_pil, depth_pil, dof_mode, dof_radius, dof_samples, debug_mode) if debug_mode: - print("Step 2: Applied depth of field effect") + print("Step 3: Applied depth of field effect") - # 3. 処理用にnumpy配列に変換 + # 4. 処理用にnumpy配列に変換 img_array = np.array(img_pil).astype(float) / 255.0 - # 4. トーン調整を適用 + # 5. 明るさ調整を適用 if debug_mode: - print(f"Step 4: Applying brightness boost: {brightness}") - img_array = np.power(img_array, 0.7) * brightness + print(f"Step 5: Applying brightness: {final_params['brightness']:.3f}") + img_array = np.power(img_array, 0.7) * final_params['brightness'] - # 5. レベル調整を適用 - img_array = self.adjust_levels(img_array, black_level, mid_level, white_level, debug_mode) + # 6. レベル調整を適用 + img_array = self.adjust_levels( + img_array, + final_params['black_level'], + final_params['mid_level'], + final_params['white_level'], + debug_mode + ) - # 6. チャンネル調整を適用 - img_array = self.adjust_channels(img_array, red_level, green_level, blue_level, debug_mode) + # 7. チャンネル調整を適用 + img_array = self.adjust_channels( + img_array, + final_params['red_level'], + final_params['green_level'], + final_params['blue_level'], + debug_mode + ) - # 7. エンハンス処理用にPIL画像に再変換 + # 8. エンハンス処理用にPIL画像に再変換 processed = Image.fromarray((np.clip(img_array * 255.0, 0, 255)).astype(np.uint8)) - # 8. 彩度を適用 + # 9. 彩度を適用 if debug_mode: - print(f"Step 8: Applying saturation: {saturation}") + print(f"Step 9: Applying saturation: {final_params['saturation']:.3f}") enhancer = ImageEnhance.Color(processed) - processed = enhancer.enhance(saturation) + processed = enhancer.enhance(final_params['saturation']) - # 9. 最終的なコントラストを適用 + # 10. 最終的なコントラストを適用 + contrast_boost = 1.3 + if auto_analysis and auto_contrast: + # 既にコントラストが高い場合は控えめに + if analysis['contrast_ratio'] > 0.7: + contrast_boost = 1.1 + if debug_mode: - print("Step 9: Applying final contrast boost (1.3)") + print(f"Step 10: Applying final contrast boost: {contrast_boost}") enhancer = ImageEnhance.Contrast(processed) - processed = enhancer.enhance(1.3) + processed = enhancer.enhance(contrast_boost) - # 10. テンソルに再変換 + # 11. テンソルに再変換 result = pil2tensor(processed, image.shape) if debug_mode: print(f"Output tensor shape: {result.shape}, dtype: {result.dtype}") - print("=== Processing complete ===\n") + print("=== Enhanced Processing Complete ===\n") - return (result,) + return (result, analysis_report) except Exception as e: print(f"Error in apply_lighting_and_color: {str(e)}") import traceback traceback.print_exc() - return (image,) + return (image, f"Error occurred: {str(e)}") # Node registration NODE_CLASS_MAPPINGS = { @@ -276,5 +515,5 @@ NODE_CLASS_MAPPINGS = { } NODE_DISPLAY_NAME_MAPPINGS = { - "FluxLightingAndColor": "Flux Lighting & Color" + "FluxLightingAndColor": "Flux Lighting & Color (Enhanced)" }