From ec7c9f8dbecbc05f09c9985524d2bea31d57d8ae Mon Sep 17 00:00:00 2001 From: zhenghu <1831829219@qq.com> Date: Tue, 14 Apr 2026 16:24:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E7=97=85?= =?UTF-8?q?=E8=99=AB=E5=AE=B3=E4=BB=A5=E5=9B=BE=E6=90=9C=E5=9B=BE=E5=BA=94?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 基于 CLIP 模型实现图片相似度搜索(app.py / main.py) - 新增 Streamlit 可视化交互界面 - 新增 pyproject.toml、justfile、Dockerfile 项目配置 - 补充完整 README 文档(功能介绍、快速开始、Docker 部署) - 新增 .gitignore --- .gitignore | 26 +++ Dockerfile | 44 ++++ README.md | 81 ++++++- app.py | 599 +++++++++++++++++++++++++++++++++++++++++++++++++ justfile | 30 +++ main.py | 7 + pyproject.toml | 20 ++ 7 files changed, 805 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 app.py create mode 100644 justfile create mode 100644 main.py create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5dccf25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Python +__pycache__/ +*.py[cod] +*.egg-info/ +dist/ +build/ + +# Virtual environment +.venv/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Streamlit +.streamlit_cache/ + +# Ruff +.ruff_cache/ + +# OS +.DS_Store +Thumbs.db +/.doc/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6940273 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +# 使用 uv 官方镜像作为基础镜像(已包含 Python 3.14 和 uv) +FROM 172.16.102.3:30648/astral-sh/uv:python3.14-bookworm + +# 设置工作目录 +WORKDIR /app + +# 配置 apt 使用阿里云镜像源 +RUN sed -i 's/httpredir.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources + +# 安装系统依赖 +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# 复制项目配置文件和锁定文件 +COPY pyproject.toml justfile uv.lock ./ + +# 配置 uv 使用阿里云镜像源(通过环境变量) +ENV UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \ + UV_HTTP_TIMEOUT=300 + +# 安装 Python 依赖(使用 uv,锁定版本) +RUN uv sync --frozen --no-dev + +# 复制应用代码和其他文件 +COPY . . + +# 暴露端口 +EXPOSE 8000 + +# 设置环境变量 +ENV STREAMLIT_SERVER_PORT=8000 \ + STREAMLIT_SERVER_ADDRESS=0.0.0.0 \ + STREAMLIT_SERVER_ENABLE_XSRF_PROTECTION=false \ + STREAMLIT_SERVER_ENABLE_CORS=false \ + STREAMLIT_SERVER_HEADLESS=true \ + STREAMLIT_BROWSER_GATHER_USAGE_STATS=false + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8000/_stcore/health || exit 1 + +# 运行 Streamlit 应用 +CMD ["uv", "run", "streamlit", "run", "app.py", "--server.port=8000", "--server.address=0.0.0.0"] diff --git a/README.md b/README.md index 64153a3..f8e3c0f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,80 @@ -# pest-image-search +# pest-image-search 病虫害以图搜图 -病虫害以图搜图 \ No newline at end of file +基于 CLIP 视觉模型的病虫害图片相似度搜索应用。上传病虫害患处图片,系统自动提取视觉特征并检索知识库中最相似的病虫害类型,提供症状描述与防治建议。 + +## 功能特性 + +- 🖼️ 支持上传本地图片、输入图片 URL、选择示例图片三种查询方式 +- 🧠 基于 `openai/clip-vit-base-patch32` 本地视觉模型提取图像特征 +- 📊 相似度可视化条形图 +- 🏷️ 覆盖水稻、小麦、玉米、大豆、番茄、黄瓜等常见作物的病虫害知识库 +- 💡 智能推荐最可能的病虫害及防治方案 + +## 技术栈 + +- Python 3.14+ +- Streamlit 1.52.1 +- Plotly 6.5.0 +- Transformers 4.51.3 + PyTorch 2.7.0 (CLIP 模型) +- Pillow、NumPy、Requests + +## 快速开始 + +### 使用 uv(推荐) + +```bash +# 安装依赖 +uv sync + +# 运行应用 +uv run streamlit run app.py +``` + +### 使用 just + +```bash +# 查看所有可用命令 +just --list + +# 运行应用 +just run + +# 代码格式化 +just format + +# 代码检查 +just check +``` + +## Docker 部署 + +```bash +# 构建镜像 +docker build -t pest-image-search . + +# 运行容器 +docker run -p 8000:8000 pest-image-search +``` + +## 项目结构 + +``` +pest-image-search/ +├── app.py # 主应用文件(Streamlit) +├── main.py # 入口文件 +├── pyproject.toml # 项目配置 +├── justfile # 任务自动化 +├── Dockerfile # Docker 配置 +└── README.md # 项目文档 +``` + +## 使用说明 + +1. 首次启动时会自动下载 CLIP 模型(约 300MB),请保持网络畅通 +2. 加载完成后自动构建病虫害图片索引 +3. 上传或选择查询图片后点击「开始搜索」,即可获得 Top-K 相似病虫害结果 +4. 结果仅供参考,实际防治请结合田间情况或咨询农业专家 + +## 许可证 + +MIT License diff --git a/app.py b/app.py new file mode 100644 index 0000000..ca503ee --- /dev/null +++ b/app.py @@ -0,0 +1,599 @@ +""" +病虫害以图搜图 +基于 CLIP 本地模型的图片 Embedding 相似度搜索 +""" + +from __future__ import annotations + +import io +import os +from dataclasses import dataclass +from typing import Literal + +import numpy as np +import plotly.graph_objects as go +import requests +import streamlit as st +from PIL import Image +from transformers import CLIPModel, CLIPProcessor + +# ─── Page Config ──────────────────────────────────────────────────────────── +st.set_page_config( + page_title="病虫害以图搜图", + page_icon="🌿", + layout="wide", + initial_sidebar_state="expanded", +) + +# ─── Custom CSS ────────────────────────────────────────────────────────────── +st.markdown(""" + +""", unsafe_allow_html=True) + + +# ─── Knowledge Base ────────────────────────────────────────────────────────── +@dataclass(frozen=True) +class PestItem: + name: str + url: str + symptoms: str + treatment: str + crop: str + category: Literal["病害", "虫害"] + + +PEST_KNOWLEDGE: list[PestItem] = [ + PestItem( + name="水稻稻瘟病", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_151854_dc9667cf_%E6%B0%B4%E7%A8%BB%E7%A8%BB%E7%98%9F%E7%97%851.jpeg", + symptoms="叶片出现梭形或纺锤形病斑,中央灰白色,边缘褐色,严重时病斑连片导致叶片枯死", + treatment="选用抗病品种,合理施肥避免偏施氮肥,发病初期喷施三环唑或稻瘟灵", + crop="水稻", + category="病害", + ), + PestItem( + name="水稻纹枯病", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_152022_9f3124ab_%E6%B0%B4%E7%A8%BB%E7%BA%B9%E6%9E%AF%E7%97%851.jpeg", + symptoms="叶鞘和叶片上出现云纹状灰绿色至灰褐色病斑,后期病斑边缘褐色、中央灰白色", + treatment="合理密植,科学管水,发病初期喷施井冈霉素或噻呋酰胺", + crop="水稻", + category="病害", + ), + PestItem( + name="水稻胡麻叶斑病", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_151936_41fdb1dc_%E6%B0%B4%E7%A8%BB%E8%83%A1%E9%BA%BB%E5%8F%B6%E6%96%91%E7%97%851.jpeg", + symptoms="叶片上出现暗褐色芝麻粒大小的椭圆形病斑,病斑周围有黄色晕圈", + treatment="增施硅肥和钾肥提高抗病力,喷施丙环唑或咪鲜胺防治", + crop="水稻", + category="病害", + ), + PestItem( + name="小麦锈病", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_153814_3e175ca3_%E5%B0%8F%E9%BA%A6%E9%94%88%E7%97%851.jpeg", + symptoms="叶片和叶鞘上出现铁锈色粉状疱疹(夏孢子堆),后期变为黑色冬孢子堆", + treatment="种植抗锈品种,发病初期喷施三唑酮或烯唑醇,注意轮作", + crop="小麦", + category="病害", + ), + PestItem( + name="小麦赤霉病", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_152112_2e1f530e_%E5%B0%8F%E9%BA%A6%E8%B5%A4%E9%9C%89%E7%97%851.jpeg", + symptoms="穗部小穗发病,颖壳上出现水浸状褐色斑,后期产生粉红色霉层", + treatment="选用抗病品种,齐穗至扬花初期喷施多菌灵或戊唑醇", + crop="小麦", + category="病害", + ), + PestItem( + name="玉米大斑病", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_153911_ee5a72be_%E7%8E%89%E7%B1%B3%E5%A4%A7%E6%96%91%E7%97%851.jpeg", + symptoms="叶片上出现灰绿色水浸状斑点,扩展为长梭形灰褐色大型病斑", + treatment="种植抗病品种,适时早播,发病初期喷施多菌灵或代森锰锌", + crop="玉米", + category="病害", + ), + PestItem( + name="玉米小斑病", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_154001_e31a0103_%E7%8E%89%E7%B1%B3%E5%B0%8F%E6%96%91%E7%97%851.jpeg", + symptoms="叶片上出现椭圆形黄褐色小病斑,有2-3圈同心轮纹,边缘紫褐色", + treatment="轮作倒茬,清除病残体,喷施百菌清或甲基托布津", + crop="玉米", + category="病害", + ), + PestItem( + name="玉米螟", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_153938_8be05006_%E7%8E%89%E7%B1%B3%E8%9E%9F1.jpeg", + symptoms="幼虫蛀食茎秆和穗轴,茎秆上有蛀孔,孔口有虫粪,造成茎秆折断", + treatment="心叶期撒施白僵菌颗粒剂,释放赤眼蜂生物防治,大喇叭口期灌心", + crop="玉米", + category="虫害", + ), + PestItem( + name="稻飞虱", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_151643_db5e1d36_%E7%A8%BB%E9%A3%9E%E8%99%AB1.jpeg", + symptoms="稻株基部聚集大量褐色或白色小型飞虫,受害稻株发黄矮缩,严重时枯死倒伏", + treatment="合理施肥避免贪青晚熟,选用吡蚜酮或烯啶虫胺防治,保护利用天敌", + crop="水稻", + category="虫害", + ), + PestItem( + name="大豆蚜虫", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_151549_d9cf327b_%E5%A4%A7%E8%B1%86%E8%9A%9C%E8%99%AB1.jpeg", + symptoms="嫩叶和茎尖聚集大量绿色或黄色蚜虫,叶片卷缩变形,植株矮化", + treatment="保护瓢虫等天敌,百株蚜量达1000头时喷施吡虫啉或啶虫脒", + crop="大豆", + category="虫害", + ), + PestItem( + name="番茄晚疫病", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_151705_3dd8baab_%E7%95%AA%E8%8C%84%E6%99%9A%E7%96%AB%E7%97%851.jpeg", + symptoms="叶片出现水浸状暗绿色不规则病斑,潮湿时叶背面产生白色霉层,果实变褐硬化", + treatment="控制温湿度,及时通风降湿,发病初期喷施甲霜灵锰锌或霜脲氰", + crop="番茄", + category="病害", + ), + PestItem( + name="黄瓜霜霉病", + url="https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_151804_7be515fa_%E9%BB%84%E7%93%9C%E9%9C%9C%E9%9C%89%E7%97%851.jpeg", + symptoms="叶片正面出现黄色多角形病斑,叶背面潮湿时产生灰黑色霉层", + treatment="选用抗病品种,膜下滴灌降低湿度,喷施百菌清或霜霉威盐酸盐", + crop="黄瓜", + category="病害", + ), +] + +EXAMPLE_IMAGES: list[tuple[str, str]] = [ + ( + "水稻稻瘟病", + "https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_151914_4f5b8fef_%E6%B0%B4%E7%A8%BB%E7%A8%BB%E7%98%9F%E7%97%852.jpeg", + ), + ( + "番茄晚疫病", + "https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_151726_a8f31320_%E7%95%AA%E8%8C%84%E6%99%9A%E7%96%AB%E7%97%852.jpeg", + ), + ( + "小麦锈病", + "https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_153837_e8ae9f43_%E5%B0%8F%E9%BA%A6%E9%94%88%E7%97%852.jpeg", + ), + ( + "水稻纹枯病", + "https://minio.dev.maimaiag.com/crop-prod-bucket/field_photo/20260410_152050_77d568b1_%E6%B0%B4%E7%A8%BB%E7%BA%B9%E6%9E%AF%E7%97%852.jpeg", + ), +] + + +# ─── CLIP Embedder ─────────────────────────────────────────────────────────── +class CLIPEmbedder: + MODEL_NAME = "openai/clip-vit-base-patch32" + + def __init__(self) -> None: + self._processor: CLIPProcessor | None = None + self._model: CLIPModel | None = None + + def _load(self) -> tuple[CLIPProcessor, CLIPModel]: + if self._processor is None or self._model is None: + with st.spinner("首次启动正在加载 CLIP 模型,请稍候..."): + self._processor = CLIPProcessor.from_pretrained(self.MODEL_NAME) + self._model = CLIPModel.from_pretrained(self.MODEL_NAME) + return self._processor, self._model + + def embed(self, image: Image.Image) -> np.ndarray: + processor, model = self._load() + inputs = processor(images=image, return_tensors="pt") + image_features = model.get_image_features(**inputs) + vec = image_features.detach().cpu().numpy().flatten() + norm = np.linalg.norm(vec) + if norm == 0: + return vec + return vec / norm + + +@st.cache_resource(show_spinner=False) +def get_embedder() -> CLIPEmbedder: + return CLIPEmbedder() + + +# ─── Utilities ─────────────────────────────────────────────────────────────── +def load_image(source: str | io.BytesIO) -> Image.Image | None: + try: + if isinstance(source, str): + resp = requests.get(source, timeout=30) + resp.raise_for_status() + return Image.open(io.BytesIO(resp.content)).convert("RGB") + return Image.open(source).convert("RGB") + except Exception as e: + st.error(f"图片加载失败: {e}") + return None + + +def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float: + return float(np.dot(a, b)) + + +@st.cache_data(show_spinner=False) +def build_index() -> tuple[list[dict], list[str], list[str]]: + embedder = get_embedder() + items, succeeded, failed = [], [], [] + progress = st.progress(0, text="正在构建病虫害图片索引...") + total = len(PEST_KNOWLEDGE) + for i, pest in enumerate(PEST_KNOWLEDGE): + img = load_image(pest.url) + if img is None: + failed.append(pest.name) + progress.progress((i + 1) / total, text=f"索引构建中 ({i + 1}/{total})...") + continue + try: + embedding = embedder.embed(img) + items.append({ + "name": pest.name, + "url": pest.url, + "embedding": embedding, + "symptoms": pest.symptoms, + "treatment": pest.treatment, + "crop": pest.crop, + "category": pest.category, + }) + succeeded.append(pest.name) + except Exception: + failed.append(pest.name) + progress.progress((i + 1) / total, text=f"索引构建中 ({i + 1}/{total})...") + progress.empty() + return items, succeeded, failed + + +# ─── Sidebar ───────────────────────────────────────────────────────────────── +with st.sidebar: + st.markdown('', unsafe_allow_html=True) + st.markdown('', unsafe_allow_html=True) + st.markdown("
", unsafe_allow_html=True) + + st.markdown('
🖼️ 输入方式
', unsafe_allow_html=True) + input_mode = st.radio("", ["上传本地图片", "输入图片 URL", "选择示例图片"], label_visibility="collapsed") + + query_source = None + query_url = "" + + if input_mode == "上传本地图片": + uploaded = st.file_uploader("选择图片", type=["jpg", "jpeg", "png", "webp"]) + if uploaded is not None: + query_source = io.BytesIO(uploaded.getvalue()) + query_url = "" + elif input_mode == "输入图片 URL": + query_url = st.text_input("图片 URL", placeholder="https://example.com/image.jpg") + if query_url.strip(): + query_source = query_url.strip() + else: + st.markdown('
点击选择示例
', unsafe_allow_html=True) + cols = st.columns(2) + for idx, (name, url) in enumerate(EXAMPLE_IMAGES): + with cols[idx % 2]: + if st.button(name, key=f"ex_{name}"): + st.session_state.query_url = url + if "query_url" in st.session_state: + query_url = st.session_state.query_url + query_source = query_url + st.image(query_url, use_container_width=True) + + st.markdown('
⚙️ 搜索设置
', unsafe_allow_html=True) + top_k = st.slider("返回条数", 1, min(12, len(PEST_KNOWLEDGE)), 5) + + st.markdown("
", unsafe_allow_html=True) + search_clicked = st.button("开始搜索", type="primary", use_container_width=True) + + st.markdown("
", unsafe_allow_html=True) + st.markdown(""" +
+ 使用说明
+ 1. 上传病虫害患处图片
+ 2. 系统自动提取图像特征
+ 3. 与知识库比对返回相似结果
+ 4. 参考症状与防治建议 +
+ """, unsafe_allow_html=True) + +# ─── Build Index ───────────────────────────────────────────────────────────── +index_items, succeeded, failed = build_index() + +# ─── Main Layout ───────────────────────────────────────────────────────────── +st.markdown(""" +
+
病虫害以图搜图
+
+
基于 CLIP 视觉模型的病虫害相似度检索与防治建议
+""", unsafe_allow_html=True) + +# Status badges +badges = [] +if succeeded: + badges.append(f'📚 知识库 {len(succeeded)} 种') +if failed: + badges.append(f'⚠️ 索引失败 {len(failed)} 种') +if badges: + st.markdown(f"
{''.join(badges)}
", unsafe_allow_html=True) + +st.markdown("
", unsafe_allow_html=True) + +# ─── Search Logic ──────────────────────────────────────────────────────────── +if search_clicked and query_source is not None and index_items: + query_img = load_image(query_source) + if query_img is not None: + col_query, col_preview = st.columns([1, 3]) + with col_query: + st.markdown('
🔍 查询图片
', unsafe_allow_html=True) + st.image(query_img, use_container_width=True) + + with col_preview: + st.markdown('
⏳ 正在分析...
', unsafe_allow_html=True) + progress = st.progress(0, text="提取图像特征...") + + embedder = get_embedder() + query_embedding = embedder.embed(query_img) + progress.progress(50, text="比对知识库...") + + scores = [] + for item in index_items: + sim = cosine_similarity(query_embedding, item["embedding"]) + scores.append((sim, item)) + scores.sort(key=lambda x: x[0], reverse=True) + results = scores[:top_k] + progress.progress(100, text="搜索完成") + progress.empty() + + st.markdown(f'
🏆 搜索结果(Top-{len(results)})
', unsafe_allow_html=True) + + # Similarity bar chart + names = [f"{r[1]['name']}" for r in results] + sims = [r[0] * 100 for r in results] + colors = ["#c45c4a" if r[1]["category"] == "虫害" else "#4a7c59" for r in results] + + fig_bar = go.Figure() + fig_bar.add_trace(go.Bar( + x=sims, + y=names, + orientation="h", + marker=dict(color=colors, opacity=0.85, line=dict(color="rgba(0,0,0,0.08)", width=1)), + text=[f"{s:.1f}%" for s in sims], + textposition="outside", + textfont=dict(color="#5a5a5a", size=10), + )) + fig_bar.update_layout( + xaxis=dict(title="相似度 (%)", color="#5a5a5a", gridcolor="rgba(0,0,0,0.06)", range=[0, 105]), + yaxis=dict(color="#5a5a5a", gridcolor="rgba(0,0,0,0.04)", autorange="reversed"), + paper_bgcolor="rgba(0,0,0,0)", + plot_bgcolor="rgba(0,0,0,0)", + font=dict(color="#2c2c2c", size=11), + margin=dict(t=10, b=30, l=80, r=50), + height=160 + len(results) * 34, + showlegend=False, + ) + st.plotly_chart(fig_bar, use_container_width=True) + + # Result cards below + st.markdown('
📋 详细结果
', unsafe_allow_html=True) + for rank, (sim, item) in enumerate(results, 1): + with st.container(): + st.markdown(f""" +
+
+
+ +
+
+
+ {rank} + {item['name']} + 相似度 {sim*100:.1f}% +
+
+ {item['crop']} + {item['category']} +
+
+ 症状:{item['symptoms']}
+ 防治:{item['treatment']} +
+
+
+
+ """, unsafe_allow_html=True) + + # Advisory summary + if results: + best = results[0][1] + st.markdown('
💡 初步建议
', unsafe_allow_html=True) + st.markdown(f""" +
+ 系统判断该图片与 {best['name']}({best['crop']}{best['category']})最为相似,相似度 {results[0][0]*100:.1f}%
+ 建议结合田间实际情况进一步确认,参考防治方案:{best['treatment']} +
+ """, unsafe_allow_html=True) + +elif search_clicked and not index_items: + st.warning("知识库索引为空,请检查网络连接后刷新页面重试。") + +# ─── Footer ─────────────────────────────────────────────────────────────────── +st.markdown("
", unsafe_allow_html=True) +st.markdown(""" +
+ 病虫害以图搜图 · 基于 CLIP 视觉模型 · 结果仅供参考,请结合田间实际情况判断 +
+""", unsafe_allow_html=True) diff --git a/justfile b/justfile new file mode 100644 index 0000000..89fabeb --- /dev/null +++ b/justfile @@ -0,0 +1,30 @@ +# Justfile for pest-image-search 病虫害以图搜图 +# Use `just ` to run tasks + +# Default task: show available commands +default: + just --list + +# Run the Streamlit app +run: + uv run streamlit run app.py + +# Install dependencies +install: + uv add streamlit ruff plotly requests pillow numpy torch transformers + +# Format code with ruff +format: + uv run ruff format . + +# Check code with ruff +check: + uv run ruff check . + +# Run all checks and formatting +lint: + just format && just check + +# Clean up cache files +clean: + rm -rf __pycache__ .ruff_cache .streamlit_cache diff --git a/main.py b/main.py new file mode 100644 index 0000000..4cedb2e --- /dev/null +++ b/main.py @@ -0,0 +1,7 @@ +def main(): + print("Hello from pest-image-search!") + print("Run with: uv run streamlit run app.py") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f415dea --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "pest-image-search" +version = "0.1.0" +description = "病虫害以图搜图 — 基于图片 Embedding 的相似度搜索" +readme = "README.md" +requires-python = ">=3.14" +dependencies = [ + "numpy>=2.3.5", + "pillow>=11.2.1", + "plotly>=6.5.0", + "requests>=2.32.3", + "ruff>=0.14.8", + "streamlit==1.52.1", + "torch>=2.7.0", + "transformers>=4.51.3", +] + +[[tool.uv.index]] +url = "https://mirrors.aliyun.com/pypi/simple" +default = true