OFA-Image-Caption与数据库联动:构建一个可搜索的图片库管理系统
你有没有遇到过这样的烦恼?电脑里存了几千张照片,想找一张“去年夏天在海边拍的、有落日和椰子树”的照片,却只能对着文件夹列表发呆,一张张点开看,费时又费力。或者,作为一个内容创作者,素材库里的图片越来越多,管理起来简直是一场噩梦。
传统的图片管理,要么靠文件名(但谁会给每张图起个详细名字呢?),要么靠手动打标签(工作量巨大且容易遗漏)。今天,我们就来动手解决这个问题,打造一个智能的图片库管理系统。它的核心思路非常巧妙:让AI看懂你的图片,并自动生成文字描述,然后你就能像搜索文档一样,用文字来搜索图片了。
我们将使用一个叫做OFA-Image-Caption的模型作为“AI看图员”,它会自动为上传的每张图片生成一段描述性文字。然后,我们把图片和这段描述一起存进MySQL数据库。最后,做一个简单的网页,让你输入“海边落日”、“会议白板”、“可爱猫咪”这样的关键词,就能瞬间找到对应的图片。
这不仅仅是一个工具,更是一个完整的全栈项目实践,涵盖了从AI模型调用、后端业务逻辑、数据库设计到前端交互的完整链条。下面,我们就一步步把它实现出来。
1. 项目蓝图:我们要做一个什么东西?
在写代码之前,我们先在脑子里把整个项目的轮廓画出来。这样思路会更清晰,写起来也不会跑偏。
简单来说,这个系统会做三件事:
- 接收图片:提供一个网页界面,让你能方便地上传图片。
- 理解图片:后端在收到图片后,悄悄调用OFA模型,让AI“看”懂图片内容并生成一段文字描述。
- 存储与搜索:把图片文件保存到服务器,同时把图片的保存路径和AI生成的描述文字,一起记录到数据库里。当你在前端搜索框输入文字时,系统就去数据库的描述文字里匹配,然后把符合条件的图片找出来展示给你。
整个系统的数据流和工作流程,可以参考下面这个简单的示意图:
用户上传图片 -> 前端界面 | v 后端服务器接收 | v 调用OFA模型生成图片描述 | v 将图片保存到服务器文件夹 | v 将[图片路径, 描述文本]存入MySQL数据库 | v 用户输入关键词搜索 -> 前端界面 | v 后端在数据库描述中模糊匹配关键词 | v 返回匹配的图片列表 -> 前端展示接下来,我们分别从数据库、后端和前端三个部分,来看看具体怎么搭建。
2. 基石:设计数据库表结构
数据库就像系统的记忆库,设计得好不好,直接影响到后续的效率和功能。我们这里的需求很简单,主要就是存图片信息。
一张图片,我们需要记录它的唯一ID(主键)、在服务器上的存放路径、AI为它生成的描述文字,以及上传的时间。基于此,我们设计一张images表就够了。
CREATE TABLE `images` ( `id` int(11) NOT NULL AUTO_INCREMENT, `image_path` varchar(500) NOT NULL COMMENT '图片在服务器上的存储路径', `description` text COMMENT 'OFA模型生成的图片描述', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间', PRIMARY KEY (`id`), KEY `idx_description` (`description`(255)) -- 为描述字段添加前缀索引,优化搜索性能 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图片信息表';字段说明:
id: 自增主键,确保每条记录唯一。image_path: 存放图片的路径,比如/uploads/2023-10-01/abc123.jpg。注意长度要设够。description: 文本类型,用于存储OFA模型生成的描述。由于描述可能较长,所以使用TEXT类型。我们为它添加了一个前缀索引 (idx_description),这样在用关键词进行模糊搜索(LIKE)时,速度会快很多。created_at: 自动记录上传时间,方便后续管理。
3. 核心引擎:用Python搭建后端服务
后端是系统的“大脑”,负责处理所有逻辑。我们选择用Python的Flask框架,因为它轻量、灵活,适合快速构建这样的应用。
3.1 准备工作:安装依赖
首先,创建一个项目文件夹,然后安装我们需要的“工具包”。
pip install flask flask-cors torch transformers pillow mysql-connector-pythonflask: 我们的Web框架。flask-cors: 处理前端跨域请求,方便调试。torch&transformers: Hugging Face的Transformers库,用于加载和运行OFA模型。pillow: Python的图像处理库,用于处理上传的图片。mysql-connector-python: 连接和操作MySQL数据库。
3.2 构建主程序:app.py
我们将所有核心逻辑写在一个app.py文件里。代码有点长,但结构很清晰,我们分块来看。
import os from datetime import datetime from flask import Flask, request, jsonify, send_from_directory from flask_cors import CORS import mysql.connector from PIL import Image import torch from transformers import OFATokenizer, OFAModel from transformers.models.ofa.generate import sequence_generator # 初始化Flask应用 app = Flask(__name__) # 允许跨域,方便前端调试 CORS(app) # ========== 配置部分 ========== UPLOAD_FOLDER = 'static/uploads' # 图片上传后保存的文件夹 ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} # 允许的图片格式 app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 限制上传文件大小为16MB # 数据库配置 DB_CONFIG = { 'host': 'localhost', 'user': 'your_username', # 改成你的数据库用户名 'password': 'your_password', # 改成你的数据库密码 'database': 'image_search_db' # 改成你创建的数据库名 } # ========== 初始化OFA模型 ========== print("正在加载OFA模型,首次加载可能需要几分钟,请耐心等待...") # 指定模型名称 model_name = "OFA-Sys/ofa-base" # 加载分词器 tokenizer = OFATokenizer.from_pretrained(model_name) # 加载模型,并设置为评估模式(不进行训练) model = OFAModel.from_pretrained(model_name, use_cache=False) model.eval() # 将模型放到GPU上(如果可用),可以大幅加快处理速度 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) print(f"OFA模型加载完成,运行在: {device}") def generate_image_caption(image_path): """使用OFA模型为指定图片生成描述""" try: # 1. 打开并预处理图片 image = Image.open(image_path) # OFA模型需要特定的输入格式 patch_img = model.patch_resize_transform(image).unsqueeze(0).to(device) # 2. 构造输入文本提示,告诉模型我们要做“图片描述” text_input = " what does the image describe?" inputs = tokenizer([text_input], return_tensors="pt").input_ids inputs = inputs.to(device) # 3. 生成描述 with torch.no_grad(): # 不计算梯度,加快推理速度 # 生成输出序列的ID out_ids = model.generate(inputs, patch_images=patch_img, num_beams=5, no_repeat_ngram_size=3) # 4. 将输出的ID解码成人类可读的文字 caption = tokenizer.batch_decode(out_ids, skip_special_tokens=True)[0] return caption.strip() except Exception as e: print(f"生成图片描述时出错: {e}") return "无法生成描述" # ========== 工具函数 ========== def get_db_connection(): """创建并返回一个数据库连接""" return mysql.connector.connect(**DB_CONFIG) def allowed_file(filename): """检查上传的文件是否是允许的图片格式""" return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS # ========== 核心API路由 ========== @app.route('/upload', methods=['POST']) def upload_image(): """处理图片上传:保存文件 -> 生成描述 -> 存入数据库""" if 'image' not in request.files: return jsonify({'error': '没有选择文件'}), 400 file = request.files['image'] if file.filename == '': return jsonify({'error': '没有选择文件'}), 400 if file and allowed_file(file.filename): # 1. 生成一个唯一的文件名,防止覆盖 timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') file_extension = file.filename.rsplit('.', 1)[1].lower() unique_filename = f"{timestamp}_{os.urandom(4).hex()}.{file_extension}" # 2. 确保上传目录存在 os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename) # 3. 保存图片文件 file.save(file_path) print(f"图片已保存至: {file_path}") # 4. 调用OFA模型生成图片描述 print("正在使用OFA模型生成图片描述...") description = generate_image_caption(file_path) print(f"生成描述: {description}") # 5. 将图片路径和描述存入数据库 conn = get_db_connection() cursor = conn.cursor() sql = "INSERT INTO images (image_path, description) VALUES (%s, %s)" cursor.execute(sql, (file_path, description)) conn.commit() image_id = cursor.lastrowid cursor.close() conn.close() return jsonify({ 'success': True, 'message': '上传成功', 'image_id': image_id, 'description': description, 'image_url': f"/{file_path}" # 返回图片的访问URL }) return jsonify({'error': '文件格式不支持'}), 400 @app.route('/search', methods=['GET']) def search_images(): """根据关键词搜索图片描述""" keyword = request.args.get('q', '').strip() if not keyword: return jsonify({'error': '请输入搜索关键词'}), 400 conn = get_db_connection() cursor = conn.cursor(dictionary=True) # 返回字典格式的结果 # 使用LIKE进行模糊匹配,%keyword%表示包含关键词即可 sql = "SELECT id, image_path, description, created_at FROM images WHERE description LIKE %s ORDER BY created_at DESC" cursor.execute(sql, ('%' + keyword + '%',)) results = cursor.fetchall() cursor.close() conn.close() # 构造返回给前端的数据,将本地路径转换为可访问的URL images = [] for row in results: images.append({ 'id': row['id'], 'url': f"/{row['image_path']}", 'description': row['description'], 'created_at': row['created_at'].strftime('%Y-%m-%d %H:%M:%S') }) return jsonify({'success': True, 'count': len(images), 'images': images}) @app.route('/static/<path:filename>') def serve_static(filename): """提供已上传图片的静态访问服务""" return send_from_directory('static', filename) # ========== 启动应用 ========== if __name__ == '__main__': # 确保上传目录存在 os.makedirs(UPLOAD_FOLDER, exist_ok=True) print("图片搜索系统后端服务启动中...") print(f"上传文件将保存在: {os.path.abspath(UPLOAD_FOLDER)}") print(f"API地址: http://127.0.0.1:5000") app.run(debug=True, host='0.0.0.0', port=5000)代码关键点解析:
- 模型加载:
OFA-Sys/ofa-base是一个在通用任务上表现不错的模型。首次运行时会从网上下载模型文件,需要一定时间和网络。 - 图片描述生成:
generate_image_caption函数是核心。它接收图片路径,用OFA模型处理,最终返回一句描述文字,比如“a group of people sitting at a table with food”。 - 文件上传:我们保存文件时,用时间戳和随机数生成唯一文件名,避免冲突。图片保存在
static/uploads/目录下。 - 数据库操作:使用
mysql.connector执行插入和查询。搜索时使用了LIKE ‘%keyword%’进行模糊匹配,这意味着只要描述中包含关键词就会被找到。 - 静态文件服务:Flask自带了静态文件服务,通过
/static/...路径可以直接访问我们上传的图片。
运行这个后端服务,你的“AI大脑”和“数据管家”就开始工作了。
4. 用户界面:用HTML/JS打造简单前端
后端准备好了,我们需要一个界面让用户能操作。这里我们用最基础的HTML、CSS和JavaScript写一个简单但功能完整的页面。
创建一个index.html文件,代码如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>智能图片库管理系统</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', system-ui, sans-serif; } body { background-color: #f5f7fa; color: #333; line-height: 1.6; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; } header { text-align: center; margin-bottom: 40px; padding: 30px; background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); color: white; border-radius: 16px; box-shadow: 0 10px 30px rgba(106, 17, 203, 0.2); } h1 { font-size: 2.8rem; margin-bottom: 10px; } .subtitle { font-size: 1.2rem; opacity: 0.9; } main { display: grid; grid-template-columns: 1fr 2fr; gap: 30px; } @media (max-width: 768px) { main { grid-template-columns: 1fr; } } .panel { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); } .panel h2 { color: #2c3e50; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 2px solid #eee; } #uploadArea { border: 3px dashed #6a11cb; text-align: center; padding: 40px 20px; cursor: pointer; border-radius: 12px; transition: all 0.3s; margin-bottom: 20px; } #uploadArea:hover, #uploadArea.dragover { background-color: #f0e6ff; border-color: #2575fc; } #fileInput { display: none; } #preview { max-width: 100%; max-height: 300px; margin-top: 15px; border-radius: 8px; display: none; } #uploadBtn { background: #6a11cb; color: white; border: none; padding: 14px 28px; border-radius: 8px; font-size: 1rem; cursor: pointer; width: 100%; margin-top: 15px; transition: background 0.3s; } #uploadBtn:hover { background: #2575fc; } #uploadBtn:disabled { background: #ccc; cursor: not-allowed; } #status { margin-top: 15px; padding: 12px; border-radius: 8px; display: none; } .search-box { display: flex; gap: 10px; margin-bottom: 25px; } #searchInput { flex: 1; padding: 15px; border: 2px solid #ddd; border-radius: 8px; font-size: 1rem; } #searchBtn { background: #2c3e50; color: white; border: none; padding: 0 25px; border-radius: 8px; cursor: pointer; transition: background 0.3s; } #searchBtn:hover { background: #1a252f; } #resultsInfo { margin-bottom: 20px; color: #7f8c8d; } #imageGrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; } .image-card { background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 3px 10px rgba(0,0,0,0.1); transition: transform 0.3s, box-shadow 0.3s; } .image-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(0,0,0,0.15); } .image-card img { width: 100%; height: 200px; object-fit: cover; } .image-card .info { padding: 15px; } .image-card .desc { color: #555; font-size: 0.95rem; margin-bottom: 10px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .image-card .time { color: #95a5a6; font-size: 0.85rem; } .loading { text-align: center; padding: 20px; color: #6a11cb; display: none; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } </style> </head> <body> <div class="container"> <header> <h1>🧠 智能图片库管理系统</h1> <p class="subtitle">上传图片,AI自动描述,用文字搜索你的图片库</p> </header> <main> <!-- 左侧:上传面板 --> <section class="panel"> <h2>📤 上传新图片</h2> <div id="uploadArea" onclick="document.getElementById('fileInput').click()"> <p>点击选择图片,或直接拖拽到此处</p> <p><small>支持 PNG, JPG, JPEG, GIF 格式</small></p> <img id="preview" alt="图片预览"> </div> <input type="file" id="fileInput" accept="image/*"> <button id="uploadBtn" onclick="uploadImage()" disabled>开始上传并生成描述</button> <div id="status"></div> </section> <!-- 右侧:搜索与展示面板 --> <section class="panel"> <h2>🔍 搜索图片库</h2> <div class="search-box"> <input type="text" id="searchInput" placeholder="输入关键词搜索,例如:猫、日落、会议..." onkeyup="if(event.key==='Enter') searchImages()"> <button id="searchBtn" onclick="searchImages()">搜索</button> </div> <div id="resultsInfo"></div> <div class="loading" id="searchLoading">搜索中...</div> <div id="imageGrid"> <!-- 搜索结果将动态插入到这里 --> <p style="text-align: center; color: #999; grid-column: 1 / -1;">上传一些图片,然后尝试用关键词搜索吧!</p> </div> </section> </main> </div> <script> const API_BASE = 'http://127.0.0.1:5000'; // 后端API地址 let selectedFile = null; // 1. 处理文件选择与预览 document.getElementById('fileInput').addEventListener('change', function(e) { selectedFile = e.target.files[0]; const uploadBtn = document.getElementById('uploadBtn'); const preview = document.getElementById('preview'); const statusDiv = document.getElementById('status'); if (selectedFile) { // 显示预览图 const reader = new FileReader(); reader.onload = function(event) { preview.src = event.target.result; preview.style.display = 'block'; }; reader.readAsDataURL(selectedFile); uploadBtn.disabled = false; statusDiv.style.display = 'none'; // 清空状态 } else { preview.style.display = 'none'; uploadBtn.disabled = true; } }); // 2. 处理拖拽上传 const uploadArea = document.getElementById('uploadArea'); uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); if (e.dataTransfer.files.length) { document.getElementById('fileInput').files = e.dataTransfer.files; // 手动触发change事件 const event = new Event('change'); document.getElementById('fileInput').dispatchEvent(event); } }); // 3. 上传图片函数 function uploadImage() { if (!selectedFile) return; const formData = new FormData(); formData.append('image', selectedFile); const uploadBtn = document.getElementById('uploadBtn'); const statusDiv = document.getElementById('status'); uploadBtn.disabled = true; uploadBtn.textContent = '上传中...'; statusDiv.style.display = 'block'; statusDiv.className = ''; statusDiv.textContent = '正在上传并调用AI生成描述,请稍候...'; fetch(`${API_BASE}/upload`, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { statusDiv.className = 'success'; statusDiv.innerHTML = `✅ 上传成功!<br>AI描述:<strong>${data.description}</strong>`; // 上传成功后,自动清空预览和选择 document.getElementById('preview').style.display = 'none'; document.getElementById('fileInput').value = ''; selectedFile = null; uploadBtn.textContent = '开始上传并生成描述'; } else { statusDiv.className = 'error'; statusDiv.textContent = `❌ 上传失败:${data.error}`; } uploadBtn.disabled = false; }) .catch(error => { statusDiv.className = 'error'; statusDiv.textContent = '❌ 网络错误或服务器异常'; uploadBtn.disabled = false; uploadBtn.textContent = '开始上传并生成描述'; console.error('上传错误:', error); }); } // 4. 搜索图片函数 function searchImages() { const keyword = document.getElementById('searchInput').value.trim(); if (!keyword) { alert('请输入搜索关键词'); return; } const resultsInfo = document.getElementById('resultsInfo'); const imageGrid = document.getElementById('imageGrid'); const loadingDiv = document.getElementById('searchLoading'); imageGrid.innerHTML = ''; loadingDiv.style.display = 'block'; resultsInfo.textContent = `正在搜索包含“${keyword}”的图片...`; fetch(`${API_BASE}/search?q=${encodeURIComponent(keyword)}`) .then(response => response.json()) .then(data => { loadingDiv.style.display = 'none'; if (data.success) { resultsInfo.textContent = `找到 ${data.count} 张相关图片`; if (data.count === 0) { imageGrid.innerHTML = '<p style="text-align:center; color:#999; grid-column:1/-1;">未找到匹配的图片,换个关键词试试?</p>'; return; } // 动态生成图片卡片 data.images.forEach(img => { const card = document.createElement('div'); card.className = 'image-card'; card.innerHTML = ` <img src="${API_BASE}${img.url}" alt="${img.description}" loading="lazy"> <div class="info"> <p class="desc">${img.description}</p> <p class="time">${img.created_at}</p> </div> `; imageGrid.appendChild(card); }); } else { resultsInfo.textContent = `搜索失败:${data.error}`; } }) .catch(error => { loadingDiv.style.display = 'none'; resultsInfo.textContent = '搜索请求失败,请检查网络或后端服务'; console.error('搜索错误:', error); }); } </script> </body> </html>前端功能亮点:
- 双上传方式:既支持点击选择文件,也支持直接拖拽文件到上传区域,体验更友好。
- 实时预览:选择图片后,会立即在页面上显示缩略图。
- 状态反馈:上传和搜索时,有明确的加载状态和成功/失败提示。
- 响应式布局:使用CSS Grid布局,在电脑和手机上都能良好显示。
- 懒加载图片:搜索结果的图片使用了
loading=“lazy”属性,页面滚动到时再加载,提升性能。
5. 让系统跑起来:部署与使用指南
现在,我们有了数据库、后端和前端,是时候把它们组装起来了。
5.1 一步步启动系统
准备数据库:
- 启动你的MySQL服务。
- 创建一个新的数据库,比如叫
image_search_db。 - 在这个数据库中,运行我们前面提供的
CREATE TABLE语句,创建images表。
配置后端:
- 打开
app.py文件,找到DB_CONFIG部分,把‘your_username’、‘your_password’和‘image_search_db’换成你自己数据库的真实信息。 - 在终端,进入项目目录,运行
python app.py。第一次运行会下载OFA模型,请保持网络通畅并耐心等待。看到类似下面的输出,说明后端启动成功:图片搜索系统后端服务启动中... 上传文件将保存在: /你的路径/static/uploads API地址: http://127.0.0.1:5000
- 打开
运行前端:
- 由于前端是纯HTML文件,你只需要用浏览器直接打开
index.html即可。但注意,因为前端通过JavaScript调用后端API(http://127.0.0.1:5000),可能会遇到跨域问题。我们已经在后端通过flask-cors解决了。确保你的后端服务在运行。 - 更简单的方式是,在项目根目录下运行一个简单的HTTP服务器。在终端(另一个窗口)输入:
python -m http.server 8000,然后浏览器访问http://localhost:8000即可。
- 由于前端是纯HTML文件,你只需要用浏览器直接打开
5.2 开始使用你的智能图库
- 上传图片:在左侧面板,点击或拖拽上传一张图片(比如一张猫的照片)。点击上传按钮,你会看到“上传中”的提示,稍等片刻,AI生成的描述(如“a cat sitting on a couch”)和成功提示就会显示出来。
- 搜索图片:在右侧搜索框,输入关键词,比如“cat”或“沙发”,点击搜索。系统会立刻从数据库的描述中查找,并将所有包含该关键词的图片展示出来。
5.3 可能遇到的问题与解决思路
- 模型下载慢或失败:可以尝试更换网络环境,或者先手动下载模型。在Python中,可以指定本地路径
from_pretrained(‘/your/local/path/to/ofa-base’)。 - 上传图片失败:检查
static/uploads文件夹是否存在且有写入权限。检查后端控制台是否有错误日志。 - 搜索无结果:确认图片已上传成功,且数据库中有记录。检查搜索关键词是否准确,OFA生成的描述是英文,所以目前需要用英文关键词搜索(如“dog”, “car”, “person”)。如果想支持中文搜索,可以考虑在后端集成翻译功能,或将OFA替换为支持中文的多模态模型。
- 页面显示“跨域错误”:确保后端
app.py中CORS(app)已启用,并且后端服务在运行。
6. 总结与展望
跟着做下来,一个具备“以文搜图”核心功能的智能图片库管理系统就搭建完成了。这个项目虽然界面简洁,但五脏俱全,完整走通了AI模型集成、后端业务开发、数据库设计交互和前端展示的整个流程。
实际用起来,你会发现它确实能解决开头的痛点。你再也不用回忆文件名,只要记得画面里有什么,用关键词就能找出来。对于摄影师、设计师或者只是喜欢整理照片的人来说,这是一个效率利器。
当然,这只是一个起点,还有很多可以优化和扩展的方向。比如,你可以为描述文本添加中文翻译,让搜索更友好;可以增加图片标签分类管理;可以引入更强大的向量数据库进行语义搜索,而不仅仅是关键词匹配;甚至可以把前端做得更美观,做成一个真正的Web应用。
这个项目的价值在于,它清晰地展示了一项AI能力(图像理解)如何与经典的后端技术(Web框架、数据库)结合,解决一个实际存在的问题。希望这个实践能给你带来启发,不仅仅是学会了一套代码,更是理解了如何将AI“落地”到具体应用中的思路。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。