ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

图像信息隐藏毕设实战:LSB算法+Flask+Vue+MySQL全链路实现

图像信息隐藏毕设实战:LSB算法+Flask+Vue+MySQL全链路实现 简介这是一套面向高校计算机相关专业学生与信息安全入门开发者的Python图像信息隐藏技术毕业设计源码包可用于课程设计、毕业设计参考或隐写术学习实践。资源共322个文件压缩包约37.16MB包含36个py源码文件、34个js与22个css前端资源、9个html页面、1个sql数据库脚本及说明文档等前后端与数据库结构完整。项目基于Python 3.6.8、MySQL 5.7与PyCharm开发实现将机密信息嵌入图片载体、肉眼难以察觉的隐蔽传输效果涵盖图像处理算法、信息隐藏逻辑与数据库交互等核心模块。已有78人学习关注。读者可获得完整可复现的工程源码、数据库脚本与项目说明文档便于理解隐写术实现原理、梳理前后端调用流程并在此基础上进行二次开发或撰写论文。1. 图像信息隐藏毕设从一张 PNG 里塞进整份文档到底难在哪图像信息隐藏这个词第一次接触的人容易想歪以为是把文件加密后改个后缀。真做过一轮就明白它要解决的是另一件事把一份数据嵌进一张看起来完全正常的图片里图片照常能打开、照常能发出去而嵌进去的数据还能被完整取回来。毕业设计里常见的形态是「完整前后端 MySQL 说明文档」前端负责上传载体图、输入待隐藏文本、下载含密图后端负责跑嵌入与提取算法MySQL 存任务记录和用户信息。这套东西的价值不在于算法多前沿而在于它把「算法—接口—数据库—前端交互」整条链路串通了是少见的能同时练 Python 图像处理和前后端分离的项目。适合谁适合已经会写 Python 基础语法、想找一个能写进简历、答辩时又讲得出技术细节的选题的人。难点从来不是调库而是嵌入容量、图像质量和提取鲁棒性这三者之间的取舍。2. 先想清楚算法选型LSB、DCT 还是空域加密组合2.1 三种主流方案的能力边界对比图像信息隐藏的算法路线大致分三类选错了后面全是返工。空域 LSB最低有效位替换是最容易上手的把像素值最低位换成数据位人眼几乎看不出差别代码量小适合毕设快速跑通。缺点是抗攻击能力弱图片一旦被重新压缩成 JPEG嵌入的数据基本全丢。DCT离散余弦变换域方法把数据嵌到频域系数里抗压缩能力明显更强但实现复杂、容量小、调试周期长。第三种是组合方案先对秘密数据做加密或置乱再走 LSB 嵌入兼顾安全性和实现难度。对毕业设计来说我一般推荐「LSB 置乱」这条路线。理由是答辩时你能讲清楚两层逻辑置乱解决安全性LSB 解决不可见性工作量可控演示效果直观。如果导师明确要求抗压缩再上 DCT但要预留至少两周调试时间。方案嵌入容量抗压缩实现难度适合场景空域 LSB高约 1/8 图像大小弱低毕设快速落地、演示DCT 域中较强高有抗攻击指标要求LSB 置乱高弱中兼顾安全性与工作量2.2 用 Python 跑通 LSB 嵌入与提取的最小闭环先不碰前后端把算法核心跑通。下面这段代码用 Pillow 和 NumPy 实现最基础的 LSB 嵌入与提取载体图必须是 PNG 这类无损格式JPEG 会在保存时破坏最低位。import numpy as np from PIL import Image def text_to_bits(text): # 把文本转成二进制位串每个字符 8 位 return .join(format(ord(c), 08b) for c in text) def bits_to_text(bits): # 每 8 位还原成一个字符 chars [chr(int(bits[i:i8], 2)) for i in range(0, len(bits), 8)] return .join(chars) def embed(image_path, secret, out_path): img Image.open(image_path).convert(RGB) arr np.array(img) flat arr.flatten() bits text_to_bits(secret) # 用 32 位长度头记录秘密信息长度方便提取时截断 header format(len(bits), 032b) payload header bits if len(payload) len(flat): raise ValueError(载体图容量不足换大图或缩短文本) for i, b in enumerate(payload): # 把每个像素值最低位替换成数据位 flat[i] (flat[i] 0xFE) | int(b) new_arr flat.reshape(arr.shape) Image.fromarray(new_arr.astype(uint8)).save(out_path, PNG) def extract(image_path): img Image.open(image_path).convert(RGB) flat np.array(img).flatten() # 先读 32 位长度头 header_bits .join(str(flat[i] 1) for i in range(32)) length int(header_bits, 2) data_bits .join(str(flat[i] 1) for i in range(32, 32 length)) return bits_to_text(data_bits) if __name__ __main__: embed(cover.png, hello hidden world, stego.png) print(extract(stego.png))逻辑说明text_to_bits把文本逐字符转成 8 位二进制embed先写 32 位长度头再写正文这样提取时才知道该读多少位。flat[i] 0xFE是把最低位清零| int(b)再写入目标位这是 LSB 的核心操作。参数上载体图建议选 512×512 以上的 PNG容量约等于像素总数除以 8 再减去 32 位头一张 512×512 的 RGB 图能藏约 98KB 文本。跑完对比原图和含密图肉眼应该看不出差异用工具算 PSNR 一般在 50dB 以上。3. 前后端分离怎么搭Flask 接口 Vue 页面 MySQL 落库3.1 后端接口设计与数据库表结构算法跑通后把它包成 HTTP 接口。后端用 Flask 足够轻量、上手快毕设场景不需要上重型框架。核心接口三个上传载体图并嵌入、提取含密图、查询历史任务。数据库用 MySQL 存任务记录表结构设计如下。CREATE TABLE hide_task ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL, cover_name VARCHAR(255), stego_name VARCHAR(255), secret_len INT DEFAULT 0, status TINYINT DEFAULT 0 COMMENT 0待处理 1成功 2失败, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;status字段用 TINYINT 而不是字符串查询和统计都方便secret_len记录嵌入长度方便前端展示容量占用。注意create_time给默认值 CURRENT_TIMESTAMP插入时不用手动传时间少一个出错点。后端接口用 Flask 实现嵌入接口接收图片和文本调用上一节的embed函数落库后返回含密图下载地址。from flask import Flask, request, jsonify, send_file import pymysql, uuid, os app Flask(__name__) UPLOAD static/upload os.makedirs(UPLOAD, exist_okTrue) def get_conn(): # 每次请求新建连接毕设并发低够用 return pymysql.connect(host127.0.0.1, userroot, passwordyour_pwd, databasestego, charsetutf8mb4) app.route(/api/embed, methods[POST]) def api_embed(): file request.files[image] secret request.form.get(secret, ) if not secret: return jsonify({code: 1, msg: 秘密文本不能为空}) cover_path os.path.join(UPLOAD, f{uuid.uuid4().hex}.png) stego_path os.path.join(UPLOAD, f{uuid.uuid4().hex}_s.png) file.save(cover_path) try: embed(cover_path, secret, stego_path) except ValueError as e: return jsonify({code: 2, msg: str(e)}) conn get_conn() with conn.cursor() as cur: cur.execute( INSERT INTO hide_task(username, cover_name, stego_name, secret_len, status) VALUES(%s,%s,%s,%s,%s), (default, os.path.basename(cover_path), os.path.basename(stego_path), len(secret), 1)) conn.commit() conn.close() return jsonify({code: 0, url: f/static/upload/{os.path.basename(stego_path)}})逻辑说明uuid4().hex生成唯一文件名避免同名覆盖这是血泪经验早期用时间戳在高并发下会撞名。get_conn每次新建连接毕设访问量小够用如果要做连接池可以引 DBUtils但没必要为答辩加复杂度。参数上secret_len存的是字符数不是位数前端展示更直观。3.2 前端上传与提取交互的关键处理前端用 Vue 的话核心是两个页面嵌入页和提取页。嵌入页要处理文件上传和文本输入提取页要能预览含密图并展示提取结果。上传用 FormData注意不要手动设置 Content-Type让浏览器自己带 boundary。// 嵌入请求 async function doEmbed(file, secret) { const fd new FormData(); fd.append(image, file); fd.append(secret, secret); const res await fetch(/api/embed, { method: POST, body: fd }); const data await res.json(); if (data.code ! 0) { alert(data.msg); // 容量不足等错误直接提示 return; } stegoUrl.value data.url; }逻辑说明fetch不设 headers 里的 Content-Type浏览器会自动生成 multipart 边界手动设反而会导致后端解析失败这是前后端联调最常见的翻车点之一。data.code用数字约定状态0 成功、1 参数错、2 容量不足前端按码分支处理。提取页同理把含密图 POST 给/api/extract后端读最低位还原文本返回。4. 避坑与排查那些让毕设卡三天的具体问题4.1 含密图保存成 JPEG 后数据全丢现象嵌入成功提取时返回乱码或空字符串。原因JPEG 是有损压缩保存时会重新量化像素值最低位被改得面目全非。解决载体图和含密图一律用 PNG 或 BMP代码里Image.save显式指定格式别依赖后缀名推断。前端下载时也要保证服务端返回的是 PNG 字节流。4.2 MySQL 连接报 2002 socket 错误现象后端启动后第一次请求就抛error 2002 (HY000): cant connect to local MySQL server through socket。原因MySQL 服务没启动或者 pymysql 连接时 host 写成 localhost 走了 socket 而不是 TCP。解决先确认 MySQL 服务在跑然后把 host 改成127.0.0.1强制走 TCP端口显式写 3306。Linux 下还要检查 MySQL 是否只监听了 socket。4.3 中文文本提取出来是乱码现象嵌入英文正常嵌入中文提取后变成问号或乱码。原因ord(c)对中文返回的是 Unicode 码点可能超过 8 位format(ord(c), 08b)会截断或报错。解决先把文本用 UTF-8 编码成字节串再逐字节转二进制。提取时反向操作把字节串 decode 回 UTF-8。def text_to_bits(text): # 先 UTF-8 编码再逐字节转位中文才不会丢 data text.encode(utf-8) return .join(format(b, 08b) for b in data) def bits_to_text(bits): data bytes(int(bits[i:i8], 2) for i in range(0, len(bits), 8)) return data.decode(utf-8, errorsignore)4.4 容量判断没做导致数组越界现象嵌入长文本时程序崩溃或图片损坏。原因没检查 payload 长度是否超过载体图可容纳的位数直接往 flat 数组里写就越界了。解决嵌入前先算len(payload) len(flat)就抛异常前端捕获后提示用户换大图或缩短文本。这个判断必须放在写位之前不能事后补救。4.5 提取时长度头读错位现象提取结果总是多几个字符或少几个字符。原因长度头用了 32 位但提取时起始偏移写错或者嵌入和提取的位序不一致。解决把长度头的位数定义成常量嵌入和提取共用同一个常量别在两处各写一个数字。位序统一用高位在前format(n, 032b)默认就是高位在前保持一致即可。5. 把鲁棒性验证做扎实PSNR、容量与抗攻击测试毕设答辩最容易被问的就是「你怎么证明藏进去看不出来、取出来不会错」。光说「我试了没问题」不够得拿数据。我一般会做三组验证写进说明文档里答辩时直接翻。第一组是不可见性验证算 PSNR 和 SSIM。PSNR 高于 40dB 人眼基本无法察觉LSB 方案通常能到 50dB 以上。用 scikit-image 两行就能算。from skimage.metrics import peak_signal_noise_ratio as psnr from skimage.metrics import structural_similarity as ssim import numpy as np from PIL import Image cover np.array(Image.open(cover.png).convert(RGB)) stego np.array(Image.open(stego.png).convert(RGB)) print(PSNR:, psnr(cover, stego, data_range255)) print(SSIM:, ssim(cover, stego, channel_axis2, data_range255))第二组是容量测试记录不同尺寸载体图能藏多少字节做成表格放进文档。512×512 的 RGB 图理论容量约 98KB实际留出长度头和安全余量按 90KB 算比较稳。第三组是抗攻击测试这也是 LSB 的软肋要如实写。对含密图做缩放、加噪、裁剪后再提取看数据是否还能还原。结论通常是无损格式下传输没问题一旦经过有损压缩或几何变换就失效。这个结论不丢人反而说明你清楚方案的边界。如果导师要求抗攻击就在这一章引出 DCT 方案的改进方向作为后续工作。一个具体技巧提取接口加一个校验位。在长度头后面再嵌 16 位 CRC 校验码提取时先校验再返回文本校验不过就明确报「数据已损坏」而不是返回一堆乱码让用户猜。这个改动只有几行代码但让整个系统的可靠性上了一个台阶答辩时是个加分项。我自己做这类项目最大的教训是别一上来就搭前后端先把算法在 Jupyter 里跑通、把 PSNR 和容量数据测出来再包接口。顺序反了后面调 bug 时你分不清是算法错还是接口错那才叫真的踩坑。希望帮到你。本文还有配套的精品资源点击获取
返回列表