PhotoAI - 智能照片光线构图分析助手 README.md# PhotoAI - 智能照片分析与优化助手## 项目简介PhotoAI 是一款基于 Python 的智能照片分析工具结合计算机视觉技术和数字艺术理论为摄影爱好者提供专业的光线、构图分析和优化建议。## 功能特性- **光线分析**: 检测曝光、对比度、色温、亮度分布- **构图分析**: 识别三分法、对称、引导线、框架等构图法则- **智能评分**: 综合评估照片质量并给出0-100分- **优化建议**: 针对问题提供具体的改进方案- **批量处理**: 支持文件夹批量分析## 安装依赖bashpip install opencv-python numpy pillow matplotlib scikit-image## 快速开始bashpython main.py --image sample.jpgpython main.py --batch ./photos/## 项目结构photo_ai/├── main.py # 主程序入口├── config.py # 配置文件├── analyzer/ # 分析器模块│ ├── init.py│ ├── light_analyzer.py # 光线分析│ └── composition_analyzer.py # 构图分析├── core/ # 核心引擎│ ├── init.py│ └── photo_engine.py # 主分析引擎├── utils/ # 工具函数│ ├── init.py│ └── image_utils.py # 图像处理工具├── models/ # 数据模型│ ├── init.py│ └── result.py # 分析结果模型└── output/ # 输出目录## 使用场景- 摄影初学者学习构图技巧- 电商产品摄影师快速质检- 社交媒体内容创作者优化图片- 数字艺术课程实践项目 一、实际应用场景描述场景一校园摄影社团教学摄影社新成员小王拍了一组校园风景照但不确定光线和构图是否合适。使用 PhotoAI 后系统指出主体偏离三分法交点建议将塔楼移至画面右侧三分之一处帮助他快速提升作品质量。场景二电商产品拍摄某手工艺品店主拍摄产品图时经常因光线不均导致色差。PhotoAI 检测到左侧阴影过重建议补光或调整拍摄角度45°使产品展示更加专业。场景三旅游博主内容创作博主小李在旅行中拍摄大量照片回看时发现很多废片。PhotoAI 批量分析后标记出构图优秀和光线有问题的照片节省筛选时间80%。 二、引入痛点痛点 传统解决方案 局限性新手不懂构图 购买摄影书籍学习 理论与实践脱节见效慢光线判断主观 凭经验反复试拍 浪费时间成功率低后期发现废片 导入电脑查看 现场无法及时调整缺乏量化标准 请教老师点评 反馈滞后成本高 三、核心逻辑讲解光线分析算法流程输入图像 → 色彩空间转换(RGB→HSV) → 直方图分析 →亮度分布计算 → 对比度评估 → 色温检测 → 生成光线报告构图分析算法流程输入图像 → 边缘检测(Canny) → 特征提取(Hough变换) →规则匹配(三分法/对称/引导线) → 视觉权重计算 → 生成构图报告评分模型总分 光线得分×0.4 构图得分×0.4 清晰度得分×0.2 四、代码模块化实现1. 配置文件 (config.py)PhotoAI 配置文件包含分析参数、阈值设置和评分权重from dataclasses import dataclassfrom typing import Tupledataclassclass LightConfig:光线分析配置brightness_threshold_low: float 40 # 亮度过低阈值brightness_threshold_high: float 220 # 亮度过高阈值contrast_min: float 0.3 # 最小对比度saturation_min: float 0.15 # 最低饱和度color_temp_warm_range: Tuple[int, int] (2000, 4000) # 暖色调范围color_temp_cool_range: Tuple[int, int] (5000, 7000) # 冷色调范围dataclassclass CompositionConfig:构图分析配置rule_of_thirds_weight: float 0.25 # 三分法权重symmetry_weight: float 0.20 # 对称性权重leading_lines_weight: float 0.20 # 引导线权重frame_within_frame_weight: float 0.15 # 框架构图权重depth_weight: float 0.20 # 景深权重center_mass_ideal_x: float 0.5 # 理想水平重心center_mass_ideal_y: float 0.5 # 理想垂直重心dataclassclass ScoringWeights:评分权重配置light_score_weight: float 0.40 # 光线得分权重composition_score_weight: float 0.40 # 构图得分权重clarity_score_weight: float 0.20 # 清晰度得分权重# 全局配置实例LIGHT_CONFIG LightConfig()COMPOSITION_CONFIG CompositionConfig()SCORING_WEIGHTS ScoringWeights()# 支持的图像格式SUPPORTED_FORMATS [.jpg, .jpeg, .png, .bmp, .tiff, .webp]2. 数据模型 (models/result.py)分析结果数据模型定义结构化输出格式from dataclasses import dataclass, fieldfrom typing import List, Dict, Optionalfrom enum import Enumimport jsonclass SuggestionPriority(Enum):建议优先级CRITICAL critical # 严重问题IMPORTANT important # 重要建议OPTIONAL optional # 可选优化dataclassclass LightAnalysisResult:光线分析结果overall_score: float # 光线总评分 0-100brightness_score: float # 亮度评分contrast_score: float # 对比度评分color_balance_score: float # 色彩平衡评分exposure_status: str # 曝光状态: under/over/normaldominant_color_temp: str # 主导色温: warm/cool/neutralissues: List[str] # 检测到的问题suggestions: List[Dict] # 优化建议列表dataclassclass CompositionAnalysisResult:构图分析结果overall_score: float # 构图总评分 0-100rule_of_thirds_score: float # 三分法得分symmetry_score: float # 对称性得分leading_lines_score: float # 引导线得分frame_within_frame_score: float # 框架构图得分depth_score: float # 景深得分detected_rules: List[str] # 检测到的构图法则subject_position: Dict # 主体位置 {x, y, region}issues: List[str] # 检测到的问题suggestions: List[Dict] # 优化建议列表dataclassclass PhotoAnalysisResult:完整照片分析结果image_path: str # 图像路径image_name: str # 图像名称light_result: LightAnalysisResult # 光线分析结果composition_result: CompositionAnalysisResult # 构图分析结果clarity_score: float # 清晰度评分total_score: float # 综合评分analysis_time: float # 分析耗时(秒)timestamp: str # 分析时间戳def to_dict(self) - Dict:转换为字典格式return {image_info: {path: self.image_path,name: self.image_name,analysis_time: f{self.analysis_time:.2f}s,timestamp: self.timestamp},light_analysis: {score: round(self.light_result.overall_score, 1),brightness: round(self.light_result.brightness_score, 1),contrast: round(self.light_result.contrast_score, 1),color_balance: round(self.light_result.color_balance_score, 1),exposure: self.light_result.exposure_status,color_temp: self.light_result.dominant_color_temp,issues: self.light_result.issues,suggestions: self.light_result.suggestions},composition_analysis: {score: round(self.composition_result.overall_score, 1),rule_of_thirds: round(self.composition_result.rule_of_thirds_score, 1),symmetry: round(self.composition_result.symmetry_score, 1),leading_lines: round(self.composition_result.leading_lines_score, 1),frame: round(self.composition_result.frame_within_frame_score, 1),depth: round(self.composition_result.depth_score, 1),detected_rules: self.composition_result.detected_rules,subject_position: self.composition_result.subject_position,issues: self.composition_result.issues,suggestions: self.composition_result.suggestions},clarity_score: round(self.clarity_score, 1),total_score: round(self.total_score, 1)}def to_json(self) - str:转换为JSON字符串return json.dumps(self.to_dict(), ensure_asciiFalse, indent2)def get_summary(self) - str:获取简要总结return f 照片分析报告: {self.image_name}{*50}⭐ 综合评分: {self.total_score:.1f}/100 光线分析 ({self.light_result.overall_score:.1f}分):• 曝光: {self.light_result.exposure_status}• 色温: {self.light_result.dominant_color_temp}• 问题数: {len(self.light_result.issues)} 构图分析 ({self.composition_result.overall_score:.1f}分):• 检测到的法则: {, .join(self.composition_result.detected_rules)}• 问题数: {len(self.composition_result.issues)} 清晰度: {self.clarity_score:.1f}分 关键建议:{self._get_top_suggestions()}def _get_top_suggestions(self) - str:获取最重要的3条建议all_suggestions (self.light_result.suggestions self.composition_result.suggestions)# 按优先级排序priority_order {critical: 0, important: 1, optional: 2}sorted_suggestions sorted(all_suggestions,keylambda x: priority_order.get(x.get(priority, optional), 2))[:3]result for i, s in enumerate(sorted_suggestions, 1):result f {i}. [{s.get(priority, optional).upper()}] {s[text]}\nreturn result if result else 暂无建议3. 工具函数 (utils/image_utils.py)图像处理工具函数提供基础图像操作和分析功能import cv2import numpy as npfrom PIL import Image, ImageStatfrom typing import Tuple, List, Optionalimport osdef load_image(image_path: str) - Tuple[Optional[np.ndarray], Optional[str]]:加载图像文件Args:image_path: 图像文件路径Returns:(图像数组, 错误信息) - 成功时错误信息为Noneif not os.path.exists(image_path):return None, f文件不存在: {image_path}try:# 使用OpenCV读取图像image cv2.imread(image_path)if image is None:return None, f无法读取图像格式: {image_path}# 转换BGR到RGBimage_rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB)return image_rgb, Noneexcept Exception as e:return None, f加载图像失败: {str(e)}def resize_for_analysis(image: np.ndarray, max_size: int 1024) - np.ndarray:调整图像尺寸以便分析保持比例Args:image: 原始图像数组max_size: 最大边长限制Returns:调整后的图像数组height, width image.shape[:2]if max(height, width) max_size:return image# 计算缩放比例scale max_size / max(height, width)new_width int(width * scale)new_height int(height * scale)# 使用双线性插值缩放resized cv2.resize(image, (new_width, new_height), interpolationcv2.INTER_LINEAR)return resizeddef calculate_brightness_histogram(image: np.ndarray) - Dict:计算亮度直方图和统计信息Args:image: RGB图像数组Returns:亮度分析字典# 转换为灰度图gray cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)# 计算直方图hist cv2.calcHist([gray], [0], None, [256], [0, 256])hist_normalized hist.flatten() / hist.sum()# 计算统计值mean_brightness np.mean(gray)std_brightness np.std(gray)median_brightness np.median(gray)# 计算亮度分布dark_pixels np.sum(gray 64) / gray.size # 暗部像素比例mid_pixels np.sum((gray 64) (gray 192)) / gray.size # 中间调比例bright_pixels np.sum(gray 192) / gray.size # 亮部像素比例# 计算动态范围p5 np.percentile(gray, 5)p95 np.percentile(gray, 95)dynamic_range p95 - p5return {mean: mean_brightness,std: std_brightness,median: median_brightness,histogram: hist_normalized.tolist(),distribution: {dark: dark_pixels,mid: mid_pixels,bright: bright_pixels},dynamic_range: dynamic_range,p5: p5,p95: p95}def calculate_contrast_metrics(image: np.ndarray) - Dict:计算对比度相关指标Args:image: RGB图像数组Returns:对比度分析字典gray cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)# RMS对比度rms_contrast np.sqrt(np.mean((gray - np.mean(gray))**2))# Michelson对比度 (基于最大最小值)min_val, max_val, _, _ cv2.minMaxLoc(gray)if max_val min_val 0:michelson_contrast (max_val - min_val) / (max_val min_val)else:michelson_contrast 0.0# Weber对比度 (基于均值)if np.mean(gray) 0:weber_contrast (max_val - np.mean(gray)) / np.mean(gray)else:weber_contrast 0.0# 局部对比度 (使用拉普拉斯算子)laplacian cv2.Laplacian(gray, cv2.CV_64F)local_contrast np.var(laplacian)return {rms_contrast: rms_contrast,michelson_contrast: michelson_contrast,weber_contrast: weber_contrast,local_contrast: local_contrast}def analyze_color_temperature(image: np.ndarray) - Dict:分析图像色温倾向Args:image: RGB图像数组Returns:色温分析字典# 分离RGB通道b, g, r cv2.split(image.astype(np.float32))# 计算各通道均值r_mean np.mean(r)g_mean np.mean(g)b_mean np.mean(b)# 计算色温指标# 暖色调: R B, 冷色调: B Rr_b_ratio r_mean / (b_mean 1e-6)# 使用色温估计公式 (简化版)if r_b_ratio 1.1:temp_category warmtemp_value 3000 (r_b_ratio - 1.1) * 1000elif r_b_ratio 0.9:temp_category cooltemp_value 7000 - (0.9 - r_b_ratio) * 2000else:temp_category neutraltemp_value 5500# 计算饱和度max_rgb np.maximum(np.maximum(r, g), b)min_rgb np.minimum(np.minimum(r, g), b)saturation np.mean((max_rgb - min_rgb) / (max_rgb 1e-6))return {category: temp_category,estimated_kelvin: int(temp_value),r_b_ratio: r_b_ratio,saturation: saturation,channel_means: {red: float(r_mean),green: float(g_mean),blue: float(b_mean)}}def detect_edges(image: np.ndarray, low_threshold: int 50, high_threshold: int 150) - np.ndarray:边缘检测Args:image: RGB图像数组low_threshold: Canny低阈值high_threshold: Canny高阈值Returns:边缘图像数组gray cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)edges cv2.Canny(gray, low_threshold, high_threshold)return edgesdef find_dominant_colors(image: np.ndarray, n_colors: int 5) - List[Dict]:查找图像主要颜色Args:image: RGB图像数组n_colors: 要提取的颜色数量Returns:主要颜色列表每项包含颜色和占比# 缩小图像以加速处理small_image cv2.resize(image, (100, 100))pixels small_image.reshape(-1, 3).astype(np.float32)# 使用K-means聚类找主要颜色criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)_, labels, centers cv2.kmeans(pixels, n_colors, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)# 计算每个颜色的占比unique, counts np.unique(labels, return_countsTrue)percentages counts / len(labels)# 按占比排序sorted_indices np.argsort(percentages)[::-1]colors []for idx in sorted_indices:color centers[idx].astype(int).tolist()percentage float(percentages[idx])colors.append({rgb: color,hex: f#{color[0]:02x}{color[1]:02x}{color[2]:02x},percentage: round(percentage * 100, 1)})return colorsdef calculate_sharpness_laplacian(image: np.ndarray) - float:使用拉普拉斯算子计算图像清晰度Args:image: RGB图像数组Returns:清晰度分数 (越高越清晰)gray cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)laplacian_var cv2.Laplacian(gray, cv2.CV_64F).var()return laplacian_vardef calculate_clarity_score(image: np.ndarray) - Dict:综合计算图像清晰度评分Args:image: RGB图像数组Returns:清晰度分析字典gray cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)# 拉普拉斯方差 (锐度)laplacian_var cv2.Laplacian(gray, cv2.CV_64F).var()# Sobel梯度 (边缘强度)sobel_x cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize3)sobel_y cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize3)gradient_magnitude np.sqrt(sobel_x**2 sobel_y**2)avg_gradient np.mean(gradient_magnitude)# Brenner梯度 (高频分量)kernel np.array([[1, 1], [1, 1]])diff cv2.filter2D(gray.astype(float), -1, kernel)brenner np.mean(diff**2)# 归一化评分sharpness_score min(100, max(0, 20 * np.log10(laplacian_var 1)))gradient_score min(100, max(0, 10 * avg_gradient))brenner_score min(100, max(0, 5 * np.log10(brenner 1)))# 综合评分clarity_score 0.5 * sharpness_score 0.3 * gradient_score 0.2 * brenner_scorereturn {clarity_score: clarity_score,sharpness: sharpness_score,gradient: gradient_score,brenner: brenner_score,laplacian_variance: laplacian_var,avg_gradient: avg_gradient}4. 光线分析器 (analyzer/light_analyzer.py)光线分析器模块负责分析照片的光线质量并提供优化建议import cv2import numpy as npfrom typing import List, Dict, Tuple, Optionalfrom dataclasses import dataclassfrom config import LIGHT_CONFIG, SCORING_WEIGHTSfrom utils.image_utils import (calculate_brightness_histogram,calculate_contrast_metrics,analyze_color_temperature,calculate_clarity_score)dataclassclass LightIssue:光线问题type: strseverity: str # low, medium, highdescription: strposition: Optional[Tuple[int, int]] Noneclass LightAnalyzer:光线分析器分析维度:1. 亮度分布 (过曝/欠曝/正常)2. 对比度 (动态范围/局部对比)3. 色彩平衡 (色温/饱和度)4. 曝光均匀性def __init__(self, config: LIGHT_CONFIG.__class__ LIGHT_CONFIG):初始化光线分析器Args:config: 光线分析配置self.config configself.issues: List[LightIssue] []def analyze(self, image: np.ndarray) - Dict:执行完整的光线分析Args:image: RGB图像数组Returns:光线分析结果字典self.issues [] # 重置问题列表# 1. 亮度分析brightness_data calculate_brightness_histogram(image)brightness_score, brightness_issues self._analyze_brightness(brightness_data)# 2. 对比度分析contrast_data calculate_contrast_metrics(image)contrast_score, contrast_issues self._analyze_contrast(contrast_data)# 3. 色彩温度分析color_data analyze_color_temperature(image)color_score, color_issues self._analyze_color(color_data)# 4. 综合评估total_score (brightness_score * 0.35 contrast_score * 0.35 color_score * 0.30)# 确定曝光状态exposure_status self._determine_exposure_status(brightness_data)# 收集所有问题和建议all_issues brightness_issues contrast_issues color_issuessuggestions self._generate_suggestions(all_issues, brightness_data, contrast_data, color_data)return {overall_score: round(total_score, 2),brightness_score: round(brightness_score, 2),contrast_score: round(contrast_score, 2),color_balance_score: round(color_score, 2),exposure_status: exposure_status,dominant_color_temp: color_data[category],issues: [issue.description for issue in all_issues],suggestions: suggestions,raw_data: {brightness: brightness_data,contrast: contrast_data,color: color_data}}def _analyze_brightness(self, data: Dict) - Tuple[float, List[LightIssue]]:分析亮度质量score 100.0issues []mean_brightness data[mean]dark_ratio data[distribution][dark]bright_ratio data[distribution][bright]dynamic_range data[dynamic_range]# 检查欠曝if mean_brightness self.config.brightness_threshold_low:penalty (self.config.brightness_threshold_low - mean_brightness) * 0.8score - penaltyissues.append(LightIssue(typeunderexposure,severityhigh if mean_brightness 25 else medium,descriptionf图像偏暗平均亮度仅{mean_brightness:.1f}建议增加曝光或补光))# 检查过曝if mean_brightness self.config.brightness_threshold_high:penalty (mean_brightness - self.config.brightness_threshold_high) * 0.6score - penaltyissues.append(LightIssue(typeoverexposure,severityhigh if mean_brightness利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛