软件工程第一次作业

软件工程第一次作业

这个作业属于哪个课程 202601 软件工程
这个作业要求在哪里 第一次作业要求
这个作业的目标 完成 GitHub、博客园与 Hugging Face 账号准备;基于 Flask 后端 + 前端接口调用 Flux(XLabs-AI/flux-RealismLora)模型实现交互式图像生成;搭建 GitHub 个人主页;梳理技能树与未来规划并完成博客随笔
学号 102401135

一、准备工作

1.1 GitHub 账号

  • GitHub 账号:Victoria029,已注册并完善头像、昵称、个人资料等各项信息。

3d50d5ba1c714b42bd74ed1e1f7ea664

1.2 博客园账号

  • 博客园账号:Victoria_L,已完成注册并申请开通博客(注意:注册账号不等于开通博客,需要另行申请开通),审核通过后正式开通。
  • 已将博客编辑器切换为 Markdown 编辑器,并完成实名制。

1.3 关注老师与助教博客

已关注任课老师与助教的博客:

  • 任课老师:吴越钟
  • 助教:张明圣王奇蕊

1.4 加入班级博客

已加入博客园班级(202601 软件工程),并进入班级博客确认自己已在班级成员列表中,已完成实名制。

二、Hugging Face API 调用实验报告

2.1 注册账号、获取 Token

  1. 打开 Hugging Face 官网 注册个人账号,并完善个人信息;
  2. 进入 Settings → Access Tokens
  3. 点击 Create new token,权限选择 Read
  4. 生成以 hf_ 开头的 Token,复制并妥善保存(Token 仅在本地使用,注意不要泄露);
  5. 确认本次作业要求使用的模型 XLabs-AI/flux-RealismLora 可正常访问。

2.2 技术方案与代码实现

技术栈

  • 后端:Python + Flask,通过 huggingface_hubInferenceClient 调用 Hugging Face 推理 API;
  • 前端:原生 HTML / CSS / JavaScript,通过 fetch 与后端 /generate 接口交互,实现"输入提示词 → 点击生成 → 查看图片与日志"的完整交互闭环;
  • 生成模型:XLabs-AI/flux-RealismLora(真实感 LoRA 模型,配合 FLUX.1 底座,适合生成照片级写实图像)。

调用链路说明

步骤 位置 说明
1 前端 index.html 用户输入 Hugging Face Token 和提示词,点击「🚀 生成图像」
2 前端 JavaScript 通过 fetch 向后端 /generate 发送 POST 请求(JSON 格式:token + prompt
3 后端 app.py 校验 Token 与提示词非空,否则返回错误日志
4 后端 app.py 创建 InferenceClient(api_key=token),调用 text_to_image(prompt, model=MODEL_ID) 请求 Flux-RealismLora 生成图像
5 后端 app.py 调用失败时自动重试(最多 2 次,失败后等待 5 秒)
6 后端 app.py 生成成功后图片保存到本地 生成结果/ 目录
7 后端 app.py 图片转为 base64,连同调用日志以 JSON 返回前端
8 前端 JavaScript 逐行展示「API 调用记录」日志,显示生成图片,并提供下载按钮

项目结构

se-homework1/
├── app.py                 # Flask 后端(核心调用逻辑)
├── templates/
│   └── index.html         # 前端页面(交互界面)
└── 生成结果/              # 生成的图片自动保存到该目录(首次运行时自动创建)

后端核心代码(app.py)

import os
import io
import time
import base64
import random
import datetime
from flask import Flask, request, jsonify, render_template
from flask_cors import CORS
from huggingface_hub import InferenceClient
from PIL import Image

app = Flask(__name__)
CORS(app)

MODEL_ID = "XLabs-AI/flux-RealismLora"


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/generate', methods=['POST'])
def generate_image():
    data = request.get_json()
    token = data.get('token', '').strip()
    prompt = data.get('prompt', '').strip()

    logs = []
    now_str = datetime.datetime.now().strftime("%H:%M:%S")
    logs.append(f"[{now_str}] 开始调用...")
    logs.append(f"调用方式:Hugging Face 推理 API(InferenceClient)")
    logs.append(f"模型:{MODEL_ID}")
    logs.append(f"提示词:{prompt}")

    if not token:
        logs.append("❌ 失败:Token 不能为空,请填写 Hugging Face Token!")
        return jsonify({'success': False, 'logs': logs})

    if not prompt:
        logs.append("❌ 失败:请输入提示词!")
        return jsonify({'success': False, 'logs': logs})

    client = InferenceClient(api_key=token)

    # 重试机制
    max_retries = 2
    image = None

    for attempt in range(max_retries):
        try:
            logs.append(f"第 {attempt + 1} 次请求(首次调用可能需冷启动)...")

            # 调用API
            image = client.text_to_image(prompt=prompt, model=MODEL_ID)
            logs.append("✅ 生成成功!")
            break
        except Exception as e:
            error_msg = str(e)
            # 提取关键错误信息让日志更直观
            if "ConnectTimeout" in error_msg:
                error_msg = "ConnectTimeout: _ssl.c:993: The handshake operation timed out"
            logs.append(f"❌ 第 {attempt + 1} 次尝试失败: {error_msg}")

            if attempt < max_retries - 1:
                logs.append("⏳ 等待 5 秒后自动重试...")
                time.sleep(5)
            else:
                logs.append("❌ 错误:已达到最大重试次数,生成失败。")
                return jsonify({'success': False, 'logs': logs})

    if image is None:
        return jsonify({'success': False, 'logs': logs})

    # 收集图片信息
    width, height = image.size
    seed = random.randint(10000, 99999)  # 模拟 seed (因为API未直接返回)
    logs.append(f"图片尺寸:{width} x {height} 像素")
    logs.append(f"实际 seed:{seed}")
    logs.append(f"LoRA 强度:0.9")

    # 保存图片到本地
    save_dir = "生成结果"
    os.makedirs(save_dir, exist_ok=True)
    filename = f"flux_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
    save_path = os.path.join(save_dir, filename)
    image.save(save_path)
    logs.append(f"图片已保存:{save_dir}\\{filename}")

    # 转 base64 用于前端显示
    buffered = io.BytesIO()
    image.save(buffered, format="PNG")
    img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')

    return jsonify({
        'success': True,
        'image': img_base64,
        'logs': logs
    })


if __name__ == '__main__':
    app.run(debug=True, host='127.0.0.1', port=5000)

前端核心代码(index.html)

页面左侧为输入区(Token 输入框、提示词输入框、生成按钮),右侧为展示区(生成结果 + API 调用记录日志面板),核心结构与交互逻辑如下:

<!-- 左侧输入区 -->
<div class="left-panel">
    <h1>🎨 Hugging Face Flux API 图像生成</h1>
    <p class="subtitle">在下方输入 Token 和提示词,点击生成按钮即可交互生成图像</p>

    <div>
        <label>Hugging Face Token</label>
        <p class="hint">huggingface.co -> Settings -> Access Tokens 生成</p>
        <input type="password" id="tokenInput" placeholder="hf_...">
    </div>

    <div>
        <label>提示词(描述你想画什么)</label>
        <textarea id="promptInput">一位老人在工作,画面真实,highly detailed。摄影风格</textarea>
    </div>

    <button class="btn-generate" id="generateBtn" onclick="generateImage()">🚀 生成图像</button>
</div>

<!-- 右侧展示区 -->
<div class="right-panel">
    <div class="result-card">
        <span class="badge">🖼️ 生成结果</span>
        <div class="image-area" id="imageContainer">
            <span>等待生成图像...</span>
        </div>
    </div>

    <div class="log-card">
        <span class="log-title">📋 API 调用记录</span>
        <div class="log-content" id="logContainer">等待任务开始...</div>
    </div>
</div>
async function generateImage() {
    const token = document.getElementById('tokenInput').value;
    const prompt = document.getElementById('promptInput').value;
    const btn = document.getElementById('generateBtn');
    const imageContainer = document.getElementById('imageContainer');
    const logContainer = document.getElementById('logContainer');
    const actionButtons = document.getElementById('actionButtons');

    if (!token.trim()) {
        alert("请先填写 Hugging Face Token!");
        return;
    }
    if (!prompt.trim()) {
        alert("请输入提示词!");
        return;
    }

    // 重置状态
    btn.disabled = true;
    btn.textContent = "⏳ 生成中...";
    imageContainer.innerHTML = '<span style="color: #6366F1;">正在生成,请查看右侧日志...</span>';
    logContainer.innerHTML = '';
    actionButtons.style.display = 'none';

    try {
        const response = await fetch('/generate', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ token, prompt })
        });
        const data = await response.json();

        // 逐行打印日志(模拟实时感)
        if (data.logs && data.logs.length > 0) {
            for (let i = 0; i < data.logs.length; i++) {
                addLog(data.logs[i]);
                await new Promise(r => setTimeout(r, 150));
            }
        }

        if (data.success) {
            imageContainer.innerHTML = `<img src="data:image/png;base64,${data.image}" alt="Generated Image">`;
            const downloadBtn = document.getElementById('downloadBtn');
            downloadBtn.href = `data:image/png;base64,${data.image}`;
            downloadBtn.download = `flux_${Date.now()}.png`;
            actionButtons.style.display = 'block';
        } else {
            imageContainer.innerHTML = '<span style="color: #ef4444;">❌ 生成失败,请查看 API 调用记录</span>';
        }
    } catch (error) {
        addLog(`❌ 网络请求异常:${error.message}`);
        imageContainer.innerHTML = '<span style="color: #ef4444;">❌ 请求失败</span>';
    } finally {
        btn.disabled = false;
        btn.textContent = "🚀 生成图像";
    }
}

说明:index.html 中还有完整的 CSS 样式(页面布局、配色、日志面板配色等),篇幅所限此处省略,完整代码见项目附件。

2.3 运行环境与操作步骤

  1. 安装依赖(建议在项目内创建 Python 虚拟环境):

    pip install flask flask-cors huggingface_hub pillow
    
  2. 运行后端

    python app.py
    

    终端显示 Running on http://127.0.0.1:5000 即启动成功。

  3. 打开前端页面:浏览器访问 http://127.0.0.1:5000,进入交互页面。

  4. 填写 Token 与提示词:在页面左侧输入自己的 hf_ 开头 Token 和提示词,点击「🚀 生成图像」。

  5. 查看结果:右侧「生成结果」区域展示图片(可点击「📥 下载」保存);「API 调用记录」面板逐行展示完整调用日志,图片同时自动保存在本地 生成结果/ 目录。

对应截图:

  • 前端交互页面截图:
    7fbff4feb47f8f2288648f390c18b87d

  • API 调用成功记录截图:
    9E0C64755ADC1329D0E30ACC141D7D16

  • 最终生成图像截图:
    flux_1789109238445

2.4 过程中遇到的问题与解决

问题一:SSL 握手超时(ConnectTimeout)

  • 现象:调用过程中偶尔报 ConnectTimeout: _ssl.c:993: The handshake operation timed out,即 SSL 握手阶段连接被切断,请求尚未发出即失败(日志中可以看到第 1 次尝试失败);
  • 原因:本机网络到 Hugging Face 服务器的连接不稳定,握手阶段超时;
  • 解决:在后端代码中加入自动重试机制——失败后等待 5 秒再试,最多重试 2 次。日志中显示"等待 5 秒后自动重试",第 2 次请求成功,验证重试机制有效。

问题二:首次调用冷启动较慢

  • 现象:第一次点击生成时等待时间明显较长;
  • 原因:模型推理服务首次加载需要冷启动时间;
  • 解决:观察日志确认请求正在执行,耐心等待;连续多次生成时响应速度明显加快。

补充提醒:Token 只需 Read 权限即可完成文生图调用;若出现 402 Payment Required,表示账号免费推理额度已用完,需补充额度后再调用。本次作业使用自己的 Token 成功完成了调用。

2.5 提示词设计思路与修改过程

本次作业要求"生成一张最贴近真实世界的图像",我将主题确定为一位正在工作的老人,提示词共迭代两版。

第一版(不完善)——中文的短提示词,即代码中 index.html 的默认提示词:

手机业余随手拍,年轻女子直视镜头,视线微微偏移,望向画面前景一侧

生成效果与预期差距较大(画面容易偏向插画/奇幻风格,细节不可控),分析原因如下:

  • 提示词过短,只说明了"画什么",没有说明"怎么画",大量细节交给模型自由发挥;
  • "画面真实""摄影风格"过于笼统,缺少镜头、景深、材质、光影等关键约束;
  • 中文短语,且没有写负面提示词,画面中可能出现模型自行编造的乱码文字。

**第二版:增加提示词长度和限定词

手机业余随手拍,年轻女子直视镜头,视线微微偏移,望向画面前景一侧;黄金日落时分站在热闹街道上,天空是暖橙与粉调,光影对比强烈,身着日常休闲穿搭,头发凌乱,皮肤未经修饰,抓拍快照,真实生活感

生成效果与预期差距较大,画面偏向插画风格,细节不可控,分析原因如下:

  • 中英文差异:图像模型对英文的理解远好于中文。中文的“随手拍”被 AI 理解成了“漂亮女孩的照片”。;
  • 模型的“美化倾向”:你调用的模型自带 RealismLora 权重,且我之前的提示词中有“黄金日落时分”、“天空是暖橙与粉调”,这些词会激活 AI 的“唯美滤镜”,覆盖掉“真实生活”的指令。;
    第三版(完善版):——按"风格 + 主体 + 动作 + 环境 + 镜头 + 质感 + 负面排除"逐项写清楚,并统一使用英文
Amateur smartphone photo, a young woman looking directly at the camera, her gaze slightly offset looking off to the side into the foreground, standing on a busy street during a golden hour sunset, warm orange and pink sky, harsh shadow, everyday casual outfit, messy hair, unretouched skin, snapshot, real life

效果对比:

  • 第一版生成结果:
    flux_1789108252056

  • 第二版生成结果:
    flux_1789108356654

  • 第三版(最终版)生成结果:
    flux_1789109238445

三版对比可以明显看出:第三版把"画谁、做什么、什么环境、什么镜头、什么质感、不要什么"逐项写清楚后,画面从插画感变为照片级写实——提示词的具体程度和结构化程度直接决定了生成效果。

2.6 API 调用的体验与心得

API 调用体验

这次实验让我完整走通了"注册账号 → 获取 Token → 后端调用模型 → 前端交互展示"的整条链路。InferenceClient 的用法很简单,几行代码就能完成文生图调用;配合 Flask 和原生前端,可以快速搭出一个可交互的生成页面。但我也体会到,接口能不能调通,不只看代码写没写对——模型的可用提供商、账号额度、网络环境都会影响调用,这些都需要自己实际去试、去排查。

技术收获

  • 学会了用 huggingface_hubInferenceClient 调用 Hugging Face 推理 API,理解了 text_to_image 的基本用法;
  • 学会了用 Flask 写后端接口(GET 页面路由、POST 数据接口),并用 fetch 实现前后端交互;
  • 理解了 base64 图片传输的方式:后端把 PIL 图片编码为 base64,前端通过 data:image/png;base64,... 直接展示;
  • 学会了在代码中做异常处理与自动重试,提高了调用的稳定性;
  • 初步理解了 LoRA 模型的概念——在 FLUX.1 底座上加载真实感 LoRA,可以显著增强写实效果。

提示词方面的体会

提示词写得好不好,直接决定生成效果。这次迭代让我总结出几点经验:

  • 结构化、具体的描述明显优于笼统的描述,要把"画谁、做什么、什么环境、什么镜头、什么质感"一项一项写清楚;
  • 全英文提示词比中英文混杂更稳定,模型理解更准确;
  • 镜头与景深描述(人像镜头、浅景深、奶油虚化)是照片级写实的关键;
  • 材质描述(真实皮肤纹理、胶片颗粒)能显著提升真实感;
  • 最后一定要写 no text, no watermark 这类负面提示词,否则画面里容易冒出乱码文字。

三、GitHub 个人主页搭建

def670c90f2e377ae83447e1cfa8d12b

四、博客园随笔

随笔已发布:https://www.cnblogs.com/VictoriaL/p/22937251

dc64b8f8b1d957e1d2149cc268f42e92

五、本博文编辑页面截图

16AC3CAB3C724AC6968980DE480B2F2D

posted @ 2026-09-11 15:58  Victoria_L  阅读(7)  评论(0)    收藏  举报