食物热量智能分析系统 - 颠覆传统瞎饿减肥法 项目概述基于计算机视觉和深度学习的智能饮食管理系统通过拍照即可获得精确的热量分析和个性化饮食建议让减肥从痛苦挨饿变为科学饮食。 实际应用场景场景一大学生减脂餐管理小李是大三学生想减肥但不知道食堂饭菜热量。用手机拍一下红烧肉盖饭系统立即显示约650大卡蛋白质25g脂肪30g建议搭配一份凉拌黄瓜减少半碗米饭。场景二健身人群营养追踪小王每天训练后吃蛋白粉鸡胸肉系统识别后分析蛋白质摄入是否达标提醒补充复合维生素避免只关注热量忽略营养均衡。场景三上班族健康午餐张女士经常点外卖系统识别麻辣烫后提示钠含量偏高建议多喝水下次选择清汤底并推荐办公室可备的健康零食清单。 行业痛点分析痛点 现状 后果热量估算困难 依赖包装标签或粗略估算 减肥计划严重偏差营养结构失衡 只关注卡路里忽略蛋白质/纤维 越减越虚代谢下降记录繁琐耗时 手动输入食物名称和重量 90%的人坚持不超1周缺乏个性化 一刀切的减肥方案 易反弹难以持续 核心逻辑架构┌─────────────────────────────────────────────────────────────┐│ 输入层 ││ 食物照片采集 │└─────────────────────────────────────────────────────────────┘↓┌─────────────────────────────────────────────────────────────┐│ 预处理层 ││ 图像增强 目标检测 │└─────────────────────────────────────────────────────────────┘↓┌─────────────────────────────────────────────────────────────┐│ 识别层 ││ CNN分类模型 (ResNet50) │└─────────────────────────────────────────────────────────────┘↓┌─────────────────────────────────────────────────────────────┐│ 数据层 ││ 食物数据库查询 (热量/营养) │└─────────────────────────────────────────────────────────────┘↓┌─────────────────────────────────────────────────────────────┐│ 分析层 ││ 热量计算 营养评估 BMI匹配 │└─────────────────────────────────────────────────────────────┘↓┌─────────────────────────────────────────────────────────────┐│ 输出层 ││ 可视化报告 个性化建议 │└─────────────────────────────────────────────────────────────┘ 核心代码实现项目结构food_calorie_analyzer/├── main.py # 主程序入口├── config.py # 配置文件├── requirements.txt # 依赖包├── README.md # 项目说明├── data/│ ├── food_database.json # 食物营养数据库│ └── model_weights.h5 # 预训练模型权重├── modules/│ ├── __init__.py│ ├── image_processor.py # 图像处理模块│ ├── food_recognizer.py # 食物识别模块│ ├── nutrition_calculator.py # 营养计算模块│ └── diet_advisor.py # 饮食建议模块└── utils/├── __init__.py└── helpers.py # 工具函数1. 配置文件 (config.py)配置文件 - 集中管理所有配置参数作者: AI Assistant版本: 1.0.0import os# 路径配置 BASE_DIR os.path.dirname(os.path.abspath(__file__))DATA_DIR os.path.join(BASE_DIR, data)MODEL_PATH os.path.join(DATA_DIR, model_weights.h5)FOOD_DB_PATH os.path.join(DATA_DIR, food_database.json)# 模型配置 IMAGE_SIZE (224, 224) # ResNet50标准输入尺寸BATCH_SIZE 32NUM_CLASSES 100 # 支持识别的食物种类数CONFIDENCE_THRESHOLD 0.7 # 识别置信度阈值# 用户配置 DEFAULT_DAILY_CALORIES {sedentary: 1800, # 久坐(办公室)light: 2000, # 轻度活动(每周运动1-3次)moderate: 2400, # 中度活动(每周运动3-5次)active: 2800 # 高度活动(每周运动6-7次)}# 营养目标比例 NUTRITION_RATIOS {protein: 0.25, # 蛋白质占比carbs: 0.50, # 碳水化合物占比fat: 0.25, # 脂肪占比fiber_min: 25, # 最小膳食纤维(g)sugar_max: 50 # 最大添加糖(g)}2. 图像处理模块 (modules/image_processor.py)图像处理模块 - 负责食物照片的预处理和增强核心功能图像加载、尺寸调整、增强处理、批量处理import cv2import numpy as npfrom PIL import Imageimport tensorflow as tffrom typing import Tuple, List, Optionalimport logging# 配置日志logging.basicConfig(levellogging.INFO)logger logging.getLogger(__name__)class ImageProcessor:图像处理器类该类封装了食物照片的所有预处理操作确保输入模型的图像质量一致。Attributes:target_size: 目标图像尺寸 (height, width)normalize: 是否进行归一化处理def __init__(self, target_size: Tuple[int, int] (224, 224),normalize: bool True):初始化图像处理器Args:target_size: 目标图像尺寸默认(224, 224)适配ResNet50normalize: 是否将像素值归一化到[0,1]self.target_size target_sizeself.normalize normalizelogger.info(f图像处理器初始化完成目标尺寸: {target_size})def load_image(self, image_path: str) - Optional[np.ndarray]:加载图像文件Args:image_path: 图像文件路径Returns:numpy数组格式的图像加载失败返回NoneRaises:FileNotFoundError: 文件不存在时抛出ValueError: 图像格式不支持时抛出if not os.path.exists(image_path):raise FileNotFoundError(f图像文件不存在: {image_path})try:# 使用OpenCV读取图像保留原始色彩空间image cv2.imread(image_path)if image is None:raise ValueError(f无法读取图像文件: {image_path})# BGR转RGB (OpenCV默认BGR格式)image cv2.cvtColor(image, cv2.COLOR_BGR2RGB)logger.debug(f成功加载图像: {image_path}, 形状: {image.shape})return imageexcept Exception as e:logger.error(f加载图像失败: {e})return Nonedef preprocess_single(self, image: np.ndarray) - np.ndarray:单张图像预处理流水线处理流程: 尺寸调整 → 中心裁剪 → 归一化 → 维度扩展Args:image: 原始图像数组 (H, W, C)Returns:预处理后的图像数组 (1, H, W, C)适合模型输入# Step 1: 调整尺寸 (保持宽高比不足部分填充)processed self._resize_with_padding(image)# Step 2: 数据增强 (训练时启用推理时可选)processed self._apply_augmentation(processed, trainingFalse)# Step 3: 像素值归一化 [0, 255] → [0, 1]if self.normalize:processed processed.astype(np.float32) / 255.0# Step 4: 添加批次维度processed np.expand_dims(processed, axis0)logger.debug(f预处理完成输出形状: {processed.shape})return processeddef _resize_with_padding(self, image: np.ndarray) - np.ndarray:保持宽高比的尺寸调整 填充使用letterbox方式处理避免图像变形影响识别精度Args:image: 原始图像Returns:调整后的图像old_height, old_width image.shape[:2]target_height, target_width self.target_size# 计算缩放比例取较小值保证完整显示scale min(target_width / old_width, target_height / old_height)# 计算新尺寸new_width int(old_width * scale)new_height int(old_height * scale)# 缩放图像resized cv2.resize(image, (new_width, new_height),interpolationcv2.INTER_LINEAR)# 创建目标画布并居中放置canvas np.zeros((target_height, target_width, 3), dtypenp.uint8)# 计算偏移量使图像居中x_offset (target_width - new_width) // 2y_offset (target_height - new_height) // 2# 将缩放后的图像放置到画布中心canvas[y_offset:y_offset new_height,x_offset:x_offset new_width] resizedreturn canvasdef _apply_augmentation(self, image: np.ndarray,training: bool False) - np.ndarray:数据增强处理训练时应用随机增强提升模型泛化能力推理时不应用随机变换保证结果一致性Args:image: 输入图像training: 是否为训练模式Returns:增强后的图像if not training:return image# 随机水平翻转 (概率50%)if np.random.random() 0.5:image cv2.flip(image, 1)# 随机亮度调整 (±10%)brightness_delta np.random.uniform(-0.1, 0.1)image np.clip(image * (1 brightness_delta), 0, 255).astype(np.uint8)# 随机对比度调整 (±10%)contrast_factor np.random.uniform(0.9, 1.1)mean_val np.mean(image)image np.clip((image - mean_val) * contrast_factor mean_val,0, 255).astype(np.uint8)return imagedef preprocess_batch(self, image_paths: List[str]) - Tuple[np.ndarray, List[str]]:批量图像预处理高效处理多张图像适用于批量预测场景Args:image_paths: 图像路径列表Returns:(预处理后的图像数组, 有效图像路径列表)valid_images []valid_paths []for path in image_paths:image self.load_image(path)if image is not None:processed self.preprocess_single(image)valid_images.append(processed)valid_paths.append(path)if not valid_images:raise ValueError(批量处理失败没有有效的图像)# 堆叠为批次张量batch np.vstack(valid_images)logger.info(f批量预处理完成共{len(valid_paths)}张图像)return batch, valid_paths# 使用示例 if __name__ __main__:# 创建处理器实例processor ImageProcessor(target_size(224, 224))# 测试单张图像处理test_image_path test_food.jpgif os.path.exists(test_image_path):original processor.load_image(test_image_path)processed processor.preprocess_single(original)print(f原始形状: {original.shape} → 处理后形状: {processed.shape})3. 食物识别模块 (modules/food_recognizer.py)食物识别模块 - 基于深度学习的图像分类器核心功能加载预训练模型、食物类别预测、置信度评估import jsonimport numpy as npimport tensorflow as tffrom typing import Dict, List, Tuple, Optionalfrom datetime import datetimeimport logging# 配置日志logging.basicConfig(levellogging.INFO)logger logging.getLogger(__name__)class FoodRecognizer:食物识别器类基于ResNet50的迁移学习模型专门针对100种常见食物进行微调。Attributes:model: TensorFlow/Keras模型实例class_names: 食物类别名称列表confidence_threshold: 预测置信度阈值def __init__(self, model_path: str,class_names_path: str,confidence_threshold: float 0.7):初始化食物识别器Args:model_path: 预训练模型权重路径class_names_path: 食物类别名称JSON文件路径confidence_threshold: 置信度阈值低于此值的结果将被过滤self.confidence_threshold confidence_thresholdself.model Noneself.class_names []# 加载类别名称self._load_class_names(class_names_path)# 构建并加载模型self._build_and_load_model(model_path)logger.info(f食物识别器初始化完成支持{len(self.class_names)}种食物)def _load_class_names(self, path: str) - None:加载食物类别名称Args:path: JSON文件路径try:with open(path, r, encodingutf-8) as f:data json.load(f)self.class_names data.get(classes, [])logger.info(f成功加载{len(self.class_names)}个食物类别)except Exception as e:logger.warning(f加载类别名称失败使用默认类别: {e})# 使用默认类别作为后备self.class_names [apple, banana, orange, rice, noodle,chicken_breast, pork_belly, beef_steak,tomato_egg, mapo_tofu, kung_pao_chicken]def _build_and_load_model(self, model_path: str) - None:构建模型架构并加载预训练权重采用ResNet50作为骨干网络顶层替换为自定义分类头- GlobalAveragePooling2D- Dense(512, ReLU)- Dropout(0.5)- Dense(num_classes, Softmax)Args:model_path: 模型权重文件路径try:# 构建基础模型 (不包含顶层)base_model tf.keras.applications.ResNet50(weightsNone, # 不使用ImageNet预训练权重include_topFalse,input_shape(224, 224, 3))# 冻结骨干网络的前面层可选用于迁移学习for layer in base_model.layers[:-20]:layer.trainable False# 构建自定义分类头x base_model.outputx tf.keras.layers.GlobalAveragePooling2D()(x)x tf.keras.layers.Dense(512, activationrelu)(x)x tf.keras.layers.Dropout(0.5)(x)predictions tf.keras.layers.Dense(len(self.class_names),activationsoftmax)(x)# 组装完整模型self.model tf.keras.Model(inputsbase_model.input, outputspredictions)# 加载训练好的权重self.model.load_weights(model_path)# 编译模型推理模式下主要用于warmupself.model.compile(optimizeradam,losscategorical_crossentropy,metrics[accuracy])logger.info(f模型加载成功参数量: {self.model.count_params():,})except Exception as e:logger.error(f模型加载失败: {e})raise RuntimeError(f无法加载模型权重: {model_path})def predict(self, preprocessed_image: np.ndarray,top_k: int 5) - List[Dict]:对预处理后的图像进行食物识别Args:preprocessed_image: 预处理后的图像数组 (1, H, W, C)top_k: 返回置信度最高的前K个结果Returns:识别结果列表每项包含食物名称和置信度示例: [{food_name: apple, confidence: 0.95}, ...]if self.model is None:raise RuntimeError(模型未正确初始化)# 执行预测start_time datetime.now()predictions self.model.predict(preprocessed_image, verbose0)inference_time (datetime.now() - start_time).total_seconds() * 1000# 解析预测结果results []for i in range(min(top_k, len(self.class_names))):confidence float(predictions[0][i])if confidence self.confidence_threshold:results.append({food_name: self.class_names[i],confidence: round(confidence, 4),rank: i 1})# 按置信度降序排序results.sort(keylambda x: x[confidence], reverseTrue)logger.debug(f预测完成耗时{inference_time:.2f}msf最高置信度: {results[0][confidence] if results else 0})return resultsdef predict_from_path(self, image_path: str,top_k: int 5) - List[Dict]:从图像路径直接进行预测便捷方法Args:image_path: 图像文件路径top_k: 返回前K个结果Returns:识别结果列表from .image_processor import ImageProcessorprocessor ImageProcessor()image processor.load_image(image_path)if image is None:raise ValueError(f无法加载图像: {image_path})preprocessed processor.preprocess_single(image)return self.predict(preprocessed, top_k)def get_model_info(self) - Dict:获取模型信息Returns:包含模型详细信息的字典if self.model is None:return {status: not_initialized}return {status: ready,num_classes: len(self.class_names),input_shape: self.model.input_shape[1:],output_shape: self.model.output_shape[1:],total_params: self.model.count_params(),trainable_params: sum([tf.keras.backend.count_params(w)for w in self.model.trainable_weights]),classes: self.class_names[:10] [...] # 只显示前10个}# 使用示例 if __name__ __main__:# 初始化识别器需要实际的模型文件recognizer FoodRecognizer(model_pathdata/model_weights.h5,class_names_pathdata/class_names.json,confidence_threshold0.7)# 打印模型信息info recognizer.get_model_info()print(json.dumps(info, indent2))4. 营养计算模块 (modules/nutrition_calculator.py)营养计算模块 - 基于食物识别结果计算营养成分核心功能热量查询、营养素计算、BMI评估、每日摄入统计import jsonfrom typing import Dict, List, Optional, Tuplefrom dataclasses import dataclassfrom datetime import datetimeimport logging# 配置日志logging.basicConfig(levellogging.INFO)logger logging.getLogger(__name__)dataclassclass NutritionResult:营养计算结果数据类用于存储单次食物识别的营养分析结果Attributes:food_name: 食物名称confidence: 识别置信度quantity_grams: 估算重量克calories: 热量千卡protein: 蛋白质克carbs: 碳水化合物克fat: 脂肪克fiber: 膳食纤维克sugar: 糖分克sodium: 钠含量毫克timestamp: 记录时间戳food_name: strconfidence: floatquantity_grams: floatcalories: floatprotein: floatcarbs: floatfat: floatfiber: floatsugar: floatsodium: floattimestamp: strdef to_dict(self) - Dict:转换为字典格式return {food_name: self.food_name,confidence: round(self.confidence, 4),quantity_grams: round(self.quantity_grams, 1),calories: round(self.calories, 1),protein: round(self.protein, 1),carbs: round(self.carbs, 1),fat: round(self.fat, 1),fiber: round(self.fiber, 1),sugar: round(self.sugar, 1),sodium: round(self.sodium, 1),timestamp: self.timestamp}class NutritionCalculator:营养计算器类整合食物数据库和营养学算法提供全面的营养分析功能。Attributes:food_db: 食物营养数据库bmi_standards: BMI标准参考值def __init__(self, food_db_path: str):初始化营养计算器Args:food_db_path: 食物营养数据库JSON文件路径self.food_db self._load_food_database(food_db_path)self.bmi_standards {underweight: (0, 18.5),normal: (18.5, 24.0),overweight: (24.0, 28.0),obese: (28.0, float(inf))}logger.info(f营养计算器初始化完成数据库包含{len(self.food_db)}种食物)def _load_food_database(self, path: str) - Dict:加载食物营养数据库数据库格式示例:{apple: {calories_per_100g: 52,protein_per_100g: 0.3,carbs_per_100g: 14,fat_per_100g: 0.2,fiber_per_100g: 2.4,sugar_per_100g: 10,sodium_per_100g: 1,category: fruit,glycemic_index: 36},...}Args:path: 数据库文件路径Returns:食物营养数据字典try:with open(path, r, encodingutf-8) as f:database json.load(f)logger.info(f成功加载食物数据库: {path})return databaseexcept FileNotFoundError:logger.warning(f食物数据库文件不存在: {path}使用内置简化数据库)return self._get_default_food_database()except json.JSONDecodeError as e:logger.error(f数据库JSON解析错误: {e})return self._get_default_food_database()def _get_default_food_database(self) - Dict:获取默认简化食物数据库后备方案Returns:包含常见食物的简化营养数据库利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛