这个作业属于哪个课程 2026-01软件工程与软件工程实践
这个作业要求在哪里 第一次个人作业要求
这个作业的目标 完成 GitHub 与博客园账号准备、调用 HuggingFace Flux 模型生成写实图像并搭建交互前端、搭建 GitHub 个人主页、撰写技能自评与学习规划随笔
学号 102401623

软件工程第一次个人作业

目录


一、准备工作

1.1 GitHub 账号

已在 https://github.com/ 注册账号,用户名为 DUJIAWEI123374,完善了个人资料。

4490a4f491ddde2a1a289a4be7494e7e

1.2 博客园账号

已在 https://www.cnblogs.com/ 注册账号,昵称 DUJIAWEI,设置了博客样式,并完成实名认证。

1.3 关注老师与助教博客

1.4 加入班级博客

已通过班级邀请链接加入 福州大学 2026-01 软件工程与软件工程实践 班级博客,并完成实名制。


二、HuggingFace API 调用

2.1 账号注册与 API 获取

访问 https://huggingface.co/ 完成注册,登录后进入 Settings → Access Tokens,点击 Create new token 创建具有推理权限的 Token。本次创建的 Token 名称为 zpw

image

Token 创建后需妥善保存,它是调用 HF Inference API 的身份凭证。

2.2 模型介绍

XLabs-AI/flux-RealismLora 是 XLabs AI 发布的、基于 Black Forest Labs FLUX.1-dev 训练的写实风格 LoRA 适配器,专门用于提升生成图像的照片级真实感。模型地址:https://huggingface.co/XLabs-AI/flux-RealismLora

该模型支持通过 Hugging Face Inference Providers(fal-ai、replicate 等服务商)调用,也可通过社区公共 Space 使用。

2.3 环境准备

  • 操作系统:Windows 11
  • Python 版本:3.14
  • 依赖安装:
pip install flask huggingface_hub gradio_client Pillow requests

网络说明:国内网络直连 Hugging Face 的 inference 域名会被阻断,本机通过 SakuraCat 本地代理(127.0.0.1:12450)出网,因此在代码中设置 HTTP_PROXY / HTTPS_PROXY 环境变量。

2.4 后端代码(app.py)

后端采用 Flask,提供 GET /(渲染前端页面)和 POST /generate(接收提示词并调用模型)两个接口。设计了双通道自动回退机制:优先使用 HF Inference API(fal-ai),额度耗尽时自动回退到公共 Space API。

import os
os.environ["HTTP_PROXY"] = "http://127.0.0.1:12450"
os.environ["HTTPS_PROXY"] = "http://127.0.0.1:12450"

import io, base64, time
from datetime import datetime
from pathlib import Path
from flask import Flask, request, jsonify, render_template
from huggingface_hub import InferenceClient
from gradio_client import Client as GradioClient
from PIL import Image

HF_TOKEN = "hf_************************HSnl"  # 替换为自己的 Token
MODEL_ID = "XLabs-AI/flux-RealismLora"
PROVIDER = "fal-ai"
SPACE_ID = "DamarJati/FLUX.1-RealismLora"

app = Flask(__name__)
hf_client = InferenceClient(provider=PROVIDER, api_key=HF_TOKEN)

DEFAULT_STYLE = ("photorealistic, natural lighting, detailed skin texture, "
                 "35mm photography, high detail, lifelike")

STYLE_MAP = {
    "default": DEFAULT_STYLE,
    "scene": "photorealistic landscape, golden hour lighting, ultra detailed, 35mm photography, high dynamic range",
    "animal": "photorealistic animal portrait, sharp focus on eyes, detailed fur texture, natural lighting, 85mm lens",
    "street": "photorealistic street photography, candid moment, cinematic lighting, 35mm lens, high detail",
    "none": "",
}

BASE_DIR = Path(__file__).resolve().parent
GEN_DIR = BASE_DIR / "generated"
GEN_DIR.mkdir(exist_ok=True)
LOG_FILE = BASE_DIR / "api_call_log.txt"

def write_log(status, prompt, http_info, duration, channel):
    line = (f"[{status}] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | "
            f"model={MODEL_ID} | channel={channel} | {http_info} | "
            f"{duration:.2f}s | prompt={prompt}\n")
    with open(LOG_FILE, "a", encoding="utf-8") as f:
        f.write(line)

def generate_via_hf(full_prompt):
    image = hf_client.text_to_image(prompt=full_prompt, model=MODEL_ID)
    return image, "hf-inference(fal-ai)"

def generate_via_space(full_prompt):
    client = GradioClient(SPACE_ID, verbose=False, token=HF_TOKEN)
    result = client.predict(prompt=full_prompt, cfg_scale=3.2, steps=32,
                            randomize_seed=True, seed=0, width=1024, height=768,
                            lora_scale=0.85, api_name="/run_lora")
    img_path = result[0] if isinstance(result, tuple) else result
    return Image.open(img_path).convert("RGB"), f"space({SPACE_ID})"

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

@app.route("/generate", methods=["POST"])
def generate_image():
    data = request.get_json(silent=True) or {}
    prompt = (data.get("prompt") or "").strip()
    style_key = data.get("style", "default") or "default"
    if not prompt:
        return jsonify({"error": "请输入提示词"}), 400
    style_words = STYLE_MAP.get(style_key, DEFAULT_STYLE)
    full_prompt = f"{prompt}, {style_words}" if style_words else prompt

    t0 = time.time()
    image, channel, errors = None, "", []
    try:
        image, channel = generate_via_hf(full_prompt)
    except Exception as e:
        errors.append(f"主通道[{PROVIDER}]失败: {type(e).__name__}: {str(e)[:120]}")
        try:
            image, channel = generate_via_space(full_prompt)
        except Exception as e2:
            errors.append(f"回退通道[Space]失败: {type(e2).__name__}: {str(e2)[:120]}")
    duration = time.time() - t0

    if image is None:
        write_log("FAIL", prompt, "HTTP 500", duration, "none")
        return jsonify({"error": ";".join(errors)}), 500

    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"{ts}.png"
    image.save(GEN_DIR / filename)
    buf = io.BytesIO()
    image.save(buf, format="PNG")
    img_base64 = base64.b64encode(buf.getvalue()).decode("utf-8")
    write_log("SUCCESS", prompt, "HTTP 200", duration, channel)
    return jsonify({"success": True, "image": img_base64, "filename": filename,
                    "elapsed": round(duration, 2), "full_prompt": full_prompt,
                    "channel": channel, "fallback_note": errors[0] if errors else None})

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

2.5 前端代码(index.html)

前端为单页应用,包含提示词输入框、风格预设下拉、生成按钮、图像展示区和生成历史记录,通过 fetch('/generate') 与后端通信。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Flux 写实图像生成器</title>
    <style>
        body { font-family: "Microsoft YaHei", Arial, sans-serif; background: linear-gradient(135deg, #0f172a, #1e293b); min-height: 100vh; color: #e2e8f0; display: flex; justify-content: center; padding: 40px 20px; }
        .container { width: 100%; max-width: 900px; }
        h1 { font-size: 28px; background: linear-gradient(90deg, #38bdf8, #818cf8); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
        .card { background: rgba(30,41,59,.7); border: 1px solid rgba(148,163,184,.2); border-radius: 16px; padding: 24px; margin-bottom: 20px; }
        textarea { width: 100%; min-height: 80px; padding: 12px 14px; background: #0f172a; border: 1px solid #334155; border-radius: 10px; color: #e2e8f0; font-size: 15px; }
        button { padding: 11px 28px; font-size: 15px; font-weight: 600; background: linear-gradient(90deg, #38bdf8, #818cf8); color: #0f172a; border: none; border-radius: 10px; cursor: pointer; }
        #imageContainer { display: none; margin-top: 8px; text-align: center; }
        #imageContainer img { max-width: 100%; border-radius: 12px; border: 1px solid #334155; }
    </style>
</head>
<body>
<div class="container">
    <h1>Flux 写实图像生成器</h1>
    <p>Hugging Face API · XLabs-AI/flux-RealismLora · 交互式生成</p>
    <div class="card">
        <label>提示词(Prompt,支持中英文)</label>
        <textarea id="promptInput" placeholder="例如:an elderly Chinese fisherman with weathered skin"></textarea>
        <div style="margin:14px 0">
            <label>风格预设:</label>
            <select id="styleSelect">
                <option value="default">写实人像(默认)</option>
                <option value="scene">户外风景</option>
                <option value="animal">动物特写</option>
                <option value="street">城市街拍</option>
                <option value="none">不加增强词</option>
            </select>
            <button onclick="generate()">生成图像</button>
        </div>
        <div id="status">就绪</div>
        <div id="imageContainer"><img id="resultImg"><div id="meta"></div></div>
    </div>
    <div class="card"><label>生成记录</label><ul id="history"><li>暂无记录</li></ul></div>
</div>
<script>
const STYLE_MAP = {
    default: "photorealistic, natural lighting, detailed skin texture, 35mm photography, high detail, lifelike",
    scene: "photorealistic landscape, golden hour lighting, ultra detailed, 35mm photography, high dynamic range",
    animal: "photorealistic animal portrait, sharp focus on eyes, detailed fur texture, natural lighting, 85mm lens",
    street: "photorealistic street photography, candid moment, cinematic lighting, 35mm lens, high detail",
    none: ""
};
async function generate() {
    const prompt = document.getElementById('promptInput').value.trim();
    const style = document.getElementById('styleSelect').value;
    const status = document.getElementById('status');
    if (!prompt) { status.textContent = '请输入提示词'; return; }
    status.textContent = '生成中...';
    const res = await fetch('/generate', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({prompt, style}) });
    const data = await res.json();
    if (data.success) {
        document.getElementById('resultImg').src = 'data:image/png;base64,' + data.image;
        document.getElementById('imageContainer').style.display = 'block';
        document.getElementById('meta').textContent = '✅ 生成成功 | 耗时 ' + data.elapsed + 's | ' + data.filename;
        status.textContent = '✅ 生成成功!';
    } else { status.textContent = '❌ ' + data.error; }
}
</script>
</body>
</html>

2.6 启动与调用

python app.py
# 浏览器访问 http://127.0.0.1:5000

2.7 API 调用成功记录

Flask 控制台日志:

 * Running on http://127.0.0.1:5000
127.0.0.1 - - [10/Sep/2026 20:37:00] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [10/Sep/2026 20:37:14] "POST /generate HTTP/1.1" 200 -

api_call_log.txt 日志:

[SUCCESS] 2026-09-10 20:37:14 | model=XLabs-AI/flux-RealismLora | channel=hf-inference(fal-ai) | HTTP 200 | 8.31s | prompt=an elderly Chinese fisherman with a straw hat, weathered skin, portrait
[SUCCESS] 2026-09-10 20:50:50 | model=XLabs-AI/flux-RealismLora | channel=space(DamarJati/FLUX.1-RealismLora) | HTTP 200 | 26.68s | prompt=an old fisherman
[SUCCESS] 2026-09-10 20:56:17 | model=XLabs-AI/flux-RealismLora | channel=space(DamarJati/FLUX.1-RealismLora) | HTTP 200 | 26.69s | prompt=an old fisherman

HTTP 响应(JSON):

{
  "success": true,
  "channel": "space(DamarJati/FLUX.1-RealismLora)",
  "elapsed": 26.68,
  "filename": "20260910_205050.png",
  "full_prompt": "an old fisherman, photorealistic, natural lighting, detailed skin texture, 35mm photography, high detail, lifelike",
  "image": "<base64 图像数据>"
}

2.8 前端页面最终生成图像截图

image

前端显示:提示词「an old fisherman」+ 写实人像风格,耗时 26.69s,生成成功,完整提示词为 an old fisherman, photorealistic, natural lighting, detailed skin texture, 35mm photography, high detail, lifelike

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

写实图像生成的提示词遵循「主体 + 光线 + 镜头 + 细节 + 风格关键词」五维结构:

维度 作用 示例词
主体描述 明确画什么,越具体越好 elderly Chinese fisherman, straw hat, weathered skin
光线 决定画面氛围和真实感 natural lighting, golden hour
镜头 模拟真实相机参数 35mm photography, 85mm lens
细节 提升纹理和清晰度 detailed skin texture, high detail
风格关键词 锁定写实风格 photorealistic, lifelike

修改过程:

  • 第一版(朴素)an old fisherman — 仅描述主体,缺少光线、镜头、细节约束,真实感不足
  • 第二版(细化主体)an elderly Chinese fisherman with a straw hat, weathered skin, portrait — 增加国籍、年龄、道具、皮肤特征和构图
  • 第三版(追加写实增强词,最终版):在用户提示词后自动追加 photorealistic, natural lighting, detailed skin texture, 35mm photography, high detail, lifelike,从光线、镜头、皮肤纹理、画质四维度强化真实感

最终生成的老渔夫肖像皮肤纹理细腻、光影自然,达到照片级真实效果。

2.10 遇到的问题与解决

问题 现象 解决方案
flask_cors 未安装 后端 import 报错,服务无法启动 同源部署无需 CORS,移除该依赖
模板路径错误 index.html 不在 templates/ 目录,首页 500 将 index.html 移入 templates/
img 标签笔误 < img 多空格,图像不渲染 修正为 <img>
国内网络阻断 直连 api-inference 域名连接被拒 设置代理环境变量走 127.0.0.1:12450
402 额度耗尽 HF Inference Providers 免费额度用完 增加公共 Space(ZeroGPU)回退通道

2.11 API 调用体验与心得

  1. API 封装简洁InferenceClient.text_to_image() 一行即可完成图像生成,无需手动处理 HTTP 请求和图像解码
  2. 免费额度有限:新账号约 1~2 次图像生成即耗尽,正式使用需充值或订阅 PRO
  3. 提示词是关键:写实风格的核心在于具体——具体的主体特征、光线条件、镜头参数,追加 photorealistic 等关键词后真实感有质的提升
  4. 工程健壮性:主通道 + 回退通道的自动降级机制保证了前端交互不中断,是必要的工程实践
  5. 网络环境:国内访问 HF 推理服务必须配置代理,开发时需显式设置代理环境变量

三、GitHub 个人主页搭建

采用方案一:创建与 GitHub ID 同名的特殊仓库 DUJIAWEI123374/DUJIAWEI123374,在根目录编写 README.md 作为个人资料自述文件。

3.1 README 内容结构

  • 关于我:个人介绍、学校专业、兴趣爱好
  • 个人成果 & 专业技能:已掌握能力、感兴趣方向、自我评估
  • 未来三年发展规划:短期/中期/长期目标
  • 代码量:当前与目标
  • 联系方式

3.2 GitHub 主页截图

4490a4f491ddde2a1a289a4be7494e7e

3.3 README 核心内容

https://github.com/DUJIAWEI123374/DUJIAWEI123374


四、技能树与自我评估

4.1 已具备的专业知识与能力

  • 能力 A — 编程基础:掌握 Python、C/C++ 基本语法,能独立完成课程编程作业和小型算法题
  • 能力 B — Web 前端:了解 HTML/CSS/JavaScript,能编写简单的静态网页和交互效果
  • 能力 C — 开发工具:熟练使用 Git 进行版本控制,掌握 Markdown 文档编写,熟悉 VS Code 开发环境
  • 能力 D — AI 应用:能调用 Hugging Face API 进行图像生成,会使用 AI 工具辅助开发和学习
  • 能力 E — 文档能力:具备良好的文档编写习惯,能撰写需求说明和技术文档

4.2 感兴趣的技术方向

  • 🌐 Web 全栈开发(前端交互 + 后端服务)
  • 🤖 人工智能与大模型应用开发(API 调用、Prompt Engineering)
  • ⚙️ 软件架构与系统设计

4.3 欠缺的能力

  • 算法能力:数据结构与算法的熟练度不足,复杂问题的分析能力有待提升
  • 工程经验:缺乏大型项目的架构设计和团队协作经验
  • 测试能力:单元测试、集成测试的实践较少
  • 后端技术:对数据库、服务器部署、微服务等后端技术了解较浅
  • 英语阅读:阅读英文技术文档的速度和准确性需要提高

五、代码量

阶段 代码量 说明
截至目前 约 3000 行 课程作业 + 小项目 + 编程练习
本学期目标 8000+ 行 软件工程课程项目 + 个人项目 + AI 应用开发
毕业目标 30000+ 行 含实习项目、开源贡献与个人作品

代码量不是唯一目标,更重要的是代码质量、可维护性和工程规范。本学期将注重在项目中实践代码评审、模块化设计和文档编写。


六、课程期待

6.1 最期待学习的知识

  1. 软件需求分析与设计方法:学习如何从模糊的需求中提取出清晰、可验证的规格说明
  2. 软件架构与设计模式:理解如何设计可扩展、可维护的系统结构
  3. 团队协作与项目管理:学习敏捷开发、Git 分支策略、代码评审等工程实践
  4. 软件测试:掌握单元测试、集成测试和测试驱动开发

6.2 希望获得的收获

  • 完成一个从需求到部署的完整软件项目
  • 提升团队协作能力和沟通能力
  • 养成良好的编码规范和文档习惯
  • 建立软件工程思维,从"写代码"升级为"做软件"

七、AI 生成的软件工程学习指南与分析

7.1 AI 生成的学习指南

以下内容由 AI 工具(DeepSeek)生成。

软件工程课程学习指南

一、课程概述
软件工程是一门研究用工程化方法构建和维护有效、实用和高质量软件的学科。核心是掌握需求分析、设计、实现、测试、维护的完整生命周期。

二、学习路线

  • 第一阶段(1-4 周):掌握 UML 用例图、类图、时序图;理解需求工程
  • 第二阶段(5-8 周):学习面向对象设计原则(SOLID)和设计模式
  • 第三阶段(9-12 周):代码规范、版本控制、单元测试、TDD
  • 第四阶段(13-16 周):团队项目、敏捷开发、项目复盘

三、推荐资源

  • 教材:《软件工程:实践者的研究方法》
  • 工具:Git、VS Code、Draw.io、Jira
  • 课程:中国大学 MOOC《软件工程》

四、学习建议

  1. 动手为主,每个阶段做小项目实践
  2. 文档先行,养成写需求和设计文档的习惯
  3. 积极参与团队项目,学习沟通协作
  4. 每个项目结束后做复盘总结

7.2 分析与评价

合理之处:

  • 学习路线按四阶段划分,与课程进度基本吻合,逻辑清晰
  • 强调"动手为主"和"文档先行",符合软件工程实践性强的特点
  • 推荐的教材和工具都是行业标准,具有参考价值
  • 提到了敏捷开发和代码评审,贴近工业界实际

不足之处:

  • 内容较为通用,缺乏针对本校课程安排的具体建议
  • 没有提到 AI 工具在软件工程中的应用(如 AI 辅助编码、测试生成),而这正是当前行业的重要趋势
  • 对"代码量"和"项目实战"的量化目标不够明确
  • 未涉及 DevOps、CI/CD 等现代工程实践

对自己的帮助:

  • 四阶段学习路线可以作为本学期的学习节奏参考
  • "文档先行"和"项目复盘"的建议值得采纳,将在课程项目中实践
  • 推荐的教材《软件工程:实践者的研究方法》可以作为课外补充阅读
  • 总体而言,这份指南提供了一个不错的框架,但需要结合本校课程实际和个人情况进行调整,特别是要补充 AI 辅助开发和 DevOps 相关内容

八、总结

本次作业完成了以下工作:

  1. 准备工作:注册并完善了 GitHub(DUJIAWEI123374)和博客园账号,关注了老师和助教博客,加入了班级博客
  2. HuggingFace API 调用:成功调用 Flux 写实模型生成了照片级真实图像,搭建了 Flask + 前端的交互生成系统,实现了双通道自动回退机制
  3. GitHub 个人主页:创建了同名仓库并编写了内容充实的 README,包含个人介绍、技能、自我评估和三年规划
  4. 技能自评与规划:梳理了当前技能树,明确了优势与不足,制定了代码量目标和课程期待
  5. AI 学习指南:使用 AI 生成了软件工程学习指南并进行了分析评价

通过本次作业,对软件工程课程的全貌有了初步认识,也在 AI 应用开发中积累了 API 调用、前后端联调和工程化的实践经验。
image

posted on 2026-09-10 21:29  DUJIAWEI  阅读(13)  评论(0)    收藏  举报