""" 猫狗图片分类 Gradio 交互界面。 这个文件把图片分类 Pipeline 包装成可体验的页面,方便上传图片或选择内置示例后查看模型预测的 最大概率类别。 """ from typing import cast import gradio as gr import numpy as np from PIL import Image from deep_learning.env.resolve import display_path, resolve_path, resolve_saved from deep_learning.pipeline.services.checkpoint import describe_checkpoint_lookup, resolve_checkpoint from deep_learning.pipeline.services.model_loader import load_inference_artifact from tasks.image_classification.runner import pipeline IMAGE_SIZE = (180, 180) CLASS_NAMES = ["Cat", "Dog"] class ImageClassificationTool: def __init__( self, pipeline, inference_artifact, resource, image_size: tuple[int, int], class_names: list[str] ): self.pipeline = pipeline self.inference_artifact = inference_artifact self.resource = resource self.image_size = image_size self.class_names = class_names def classify(self, image) -> str: input_image = Image.fromarray(np.asarray(image, dtype="uint8")).convert("RGB") resized_image = input_image.resize( (self.image_size[1], self.image_size[0]) ) image_array = np.asarray(resized_image, dtype="float32") model_input = np.expand_dims(image_array, axis=0) prediction = self.inference_artifact.model(model_input, training=False) positive_probability = float(np.asarray(prediction).reshape(-1)[0]) class_index = int(positive_probability >= 0.5) probability = positive_probability if class_index == 0: probability = 1.0 - positive_probability return f"预测类别:{self.class_names[class_index]},概率:{probability:.2%}" class ImageClassificationPageAdapter: def __init__(self, pipeline): self.pipeline = pipeline self.sample_dir = resolve_path("data/dev/cats_vs_dogs/images") def list_sample_images(self) -> list[str]: return sorted( path.name for path in self.sample_dir.iterdir() if path.is_file() and path.suffix.lower() in [".jpg", ".jpeg", ".png"] ) def load_sample_image(self, sample_name: str): image_path = self.sample_dir / sample_name return np.asarray(Image.open(image_path).convert("RGB")) def extra_model_info(self) -> str: return f"**示例图片目录**: {display_path(self.sample_dir)}" def create_tool(self, inference_artifact, resource) -> ImageClassificationTool: return ImageClassificationTool( self.pipeline, inference_artifact, resource, IMAGE_SIZE, CLASS_NAMES ) class ImageClassificationAppBuilder: def __init__( self, pipeline, tool_factory, sample_images: list[str], load_sample_image, title: str = "猫狗图片分类", input_label: str = "输入图片", sample_label: str = "示例图片", output_label: str = "预测结果", extra_model_info=None ): self.pipeline = pipeline self.tool_factory = tool_factory self.sample_images = sample_images self.load_sample_image = load_sample_image self.title = title self.input_label = input_label self.sample_label = sample_label self.output_label = output_label self.extra_model_info = extra_model_info self._tool = None def _resolve_checkpoint_rule(self) -> dict: checkpoint_rule = self.pipeline.checkpoint_load_rules.resolve_test_rule( default_dirs=[resolve_saved(f"models/{self.pipeline.name}")] ) if checkpoint_rule["suffix"] is None: checkpoint_rule["suffix"] = ".keras" return checkpoint_rule def _load_inference_artifact(self) -> tuple: checkpoint_rule = self._resolve_checkpoint_rule() return load_inference_artifact( checkpoint_rule, self.pipeline.model_builder.load_inference_artifact ) def get_model_info(self) -> str: parts = [] checkpoint_rule = self._resolve_checkpoint_rule() checkpoint_path, _ = resolve_checkpoint(**checkpoint_rule) if checkpoint_path is None: lookup_info = describe_checkpoint_lookup( dirs=checkpoint_rule.get("dirs"), path=checkpoint_rule.get("path"), suffix=checkpoint_rule.get("suffix") ) raise FileNotFoundError(f"未找到模型检查点文件。查找信息: {lookup_info}") file_name = checkpoint_path.name file_size = checkpoint_path.stat().st_size parts.append(f"**模型文件**: {file_name}({file_size / (1024 * 1024):.2f} MB)") if self.extra_model_info is not None: extra_info = self.extra_model_info() if extra_info: parts.append(extra_info) return ",".join(parts) def _init_tool(self): print("正在加载图片分类模型...") inference_artifact, resource = self._load_inference_artifact() print("模型加载完成!") return self.tool_factory(inference_artifact, resource) def _ensure_tool_initialized(self) -> None: if self._tool is None: self._tool = self._init_tool() def select_sample_image(self, sample_name: str): return self.load_sample_image(sample_name) def classify_image(self, image): self._ensure_tool_initialized() return self._tool.classify(image) def create_ui(self): with gr.Blocks(title=self.title) as demo: gr.Markdown(f"# {self.title}") gr.Markdown("请选择示例图片或直接上传图片,分类时只会读取当前输入框中的图片。") try: gr.Markdown(self.get_model_info()) except Exception as e: gr.Markdown(f"**模型信息加载失败**: {str(e)}") with gr.Row(): with gr.Column(): input_image = gr.Image( label=self.input_label, type="numpy" ) sample_name = gr.Dropdown( choices=self.sample_images, value=cast(str | None, None), label=self.sample_label ) classify_btn = gr.Button("开始分类", variant="primary") with gr.Column(): output_text = gr.Textbox( label=self.output_label, interactive=False ) sample_name.change( fn=self.select_sample_image, inputs=[sample_name], outputs=[input_image] ) classify_btn.click( fn=self.classify_image, inputs=[input_image], outputs=[output_text] ) original_get_config_file = demo.get_config_file def get_config_file(): config = original_get_config_file() for component in config["components"]: if component["type"] == "dropdown" and component["props"].get("label") == self.sample_label: component["props"]["value"] = None return config demo.get_config_file = get_config_file return demo adapter = ImageClassificationPageAdapter(pipeline) app = ImageClassificationAppBuilder( pipeline=pipeline, tool_factory=adapter.create_tool, sample_images=adapter.list_sample_images(), load_sample_image=adapter.load_sample_image, extra_model_info=adapter.extra_model_info ) demo = app.create_ui() if __name__ == "__main__": from deep_learning.env.keras import enable_mixed_precision enable_mixed_precision() demo.launch()