news 2026/9/10 15:17:38

基于Python的图像信息隐藏与LSB隐写算法毕业设计解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Python的图像信息隐藏与LSB隐写算法毕业设计解析

简介:这份毕业设计项目资料面向计算机相关专业学生,围绕Python图像信息隐藏技术,提供可运行源码、MySQL数据库及说明文档,适合毕业设计选题、课程设计参考以及图像隐写算法入门实践。系统采用Python和MySQL开发,内容覆盖信息隐藏基本模型、图像隐藏性能指标、静止图像数据隐藏技术及系统搭建过程,并展示了网站登录注册、首页、新增图像隐藏、用户管理等核心模块的实现效果。压缩包大小约39.21MB,包含源码、数据库与说明文档,目录结构清晰,便于直接部署或进行功能扩展。目前已有282人学习,说明文档按照第2章综述、第3章算法实践分析与系统实现、第4章总结展望的顺序展开,层层递进,既能帮助理解隐写技术原理,也能为毕业设计论文撰写和系统二次开发提供实用的参照与支撑。

1. 图像信息隐藏:Python毕业设计要解决的核心问题

图像信息隐藏,通俗讲就是把一段秘密信息嵌入到一张正常图像中,让外人看不出图被改过。它和加密不同:加密产出乱码,隐藏让信息“消失”在像素里。这项技术用于版权水印、隐私标记、防伪溯源,也是毕业设计里视觉反馈明显、算法可扩展、代码量可控的好题目。用Python做它,优先能借助Pillow、NumPy、PyCryptodome等库,再配合SQLite存储记录,就能组成完整系统。这篇文章会按实际带项目的顺序,从算法选型、数据库设计、参数调优到文档写作,带你把它做成一个能通过答辩的毕设。

2. LSB隐写算法与Python最小实现:从像素位到可运行代码

2.1 为什么LSB是入门首选:原理与选型理由

LSB(Least Significant Bit)是图像隐写里最基础也最好解释的一块。数字图像里每个像素有R、G、B三个分量,每个分量的值在0到255之间。人眼对颜色的小幅变化很不敏感,尤其对亮度较低的位。把每个字节的最低位(第0位)替换成秘密信息的位,人眼几乎分辨不出差别。但从数学上讲,每个像素最多能藏3比特,一张1024×768的图能藏约288KB的信息,足够藏一段长文本。

常见做法里,LSB之后是DCT和DWT。DCT(离散余弦变换)适合JPEG压缩场景,水印嵌在频域系数上,抗压缩能力强;DWT(离散小波变换)多分辨率特性更好,但实现复杂、需要引入变换库。我做毕设选了LSB为主、再加一层AES加密作为进阶点,原因很简单:LSB能用手从头实现,代码好答辩;而加密部分可以单独写一个模块,难度梯度清晰。下表是三种算法的对比:

算法容量鲁棒性实现难度适合载体
LSB高(约1/8载体大小)低(易被压缩破坏)PNG、BMP
DCT中(可抗JPEG有损压缩)JPEG
DWT中高通用

实际选型时,如果你的毕设要求里有“抗JPEG压缩”或“数字水印”的字眼,建议做DCT;如果只要求“信息隐藏”和“提取恢复”,LSB就够了。后面几章我会按LSB加数据库的方式展开,DCT的思路差别主要在嵌入域,代码框架是一致的。

2.2 一个可运行的Python LSB嵌入示例

在写代码前,先确认你的python环境。我一般用Python 3.8以上版本,配合Pillow和NumPy。如果还没装,执行pip install pillow numpy就能完成基础依赖。下面这段代码是最小化的LSB嵌入示例,它把一串字节隐藏进一张PNG图片的蓝色通道最低位:

import numpy as np from PIL import Image def embed_lsb(image_path, secret_bytes, output_path): # 读取图片并转为RGB模式,保证通道数一致 img = Image.open(image_path).convert('RGB') arr = np.array(img) height, width, channels = arr.shape # 把秘密信息转为二进制位串,并拼接结束符,便于提取时知道在哪停 bits = ''.join(format(byte, '08b') for byte in secret_bytes) bits += '1111111111111110' # 结束标记:连续的1和0,极少在文本中出现 flat = arr[:, :, 2].flatten() # 取蓝色通道,一维展开 if len(bits) > len(flat): raise ValueError('秘密信息太长,请换一张更大的图片') for i, bit in enumerate(bits): # 最低位清零后按位或,写入当前bit if bit == '1': flat[i] = flat[i] | 1 else: flat[i] = flat[i] & 0xFE # 清除最低位 arr[:, :, 2] = flat.reshape((height, width)) Image.fromarray(arr).save(output_path, format='PNG') return len(bits) // 8

这段代码的关键逻辑在最后几行:flat[i] = flat[i] | 1flat[i] = flat[i] & 0xFE,前者强制把最低位变成1,后者把最低位清0。这样每次只改像素值的最低位,最多产生±1的颜色变化,肉眼根本察觉不到。参数方面,bits += '1111111111111110'是结束标记,你也可以在提取方单独传一个长度变量,但你做毕设时用标记法更方便演示。还需要注意,原始秘密字节要先经过format(byte, '08b')转成8位二进制,因为一个字节必须是8位,否则位串长度会错位。

2.3 提取代码与边界条件

有嵌入就得有提取,提取是嵌入的逆过程。下面是我的提取函数:

def extract_lsb(image_path): img = Image.open(image_path).convert('RGB') arr = np.array(img) flat = arr[:, :, 2].flatten() bits = '' for pixel in flat: bits += str(pixel & 1) # 与1做与运算,取出最低位 # 以16个比特为一组,查找结束标记 marker = '1111111111111110' idx = bits.find(marker) if idx == -1: raise ValueError('未找到结束标记,图片可能未隐藏信息或已经被修改') data_bits = bits[:idx] # 每8位一组,转回字节 secret_bytes = bytes(int(data_bits[i:i+8], 2) for i in range(0, len(data_bits), 8)) return secret_bytes

提取时的第一个坑是位顺序:嵌入时是逐位写入,提取时也必须按同一顺序读,否则恢复出的字节全是乱的。第二个坑是图片格式:提取方必须拿到PNG或BMP这类无损格式,如果中间经过JPEG压缩,蓝色通道的最低位会被压缩算法改写,结束标记找不到是常有的事。我在给学弟学妹调试时见过最多的错误是:他们嵌完信息后用微信传给另一台电脑,微信默认把图片转成JPEG,结果提取直接失败。所以提取前一定要确认图像数据是原始的,没有经过任何有损缩放或格式转换。

这一章里我们已经有了可运行的嵌入和提取代码,接下来要把它放进一个正经项目里,这就得引入数据库了。

3. 毕业设计项目结构:数据库表设计与Python模块划分

3.1 数据库为什么出现在这个题目里:需求和选型

很多同学拿到题目会问:“图像隐写不是算法题吗?为什么要数据库?”这是毕设出题人的典型思路:算法题也要能看出软件工程能力。数据库在这里不是核心算法,而是工程化的辅助模块。常见做法是记录三类数据:用户信息、嵌入历史、载体图片元数据。比如你每次嵌入了一段信息,把图片路径、秘密信息长度、嵌入时间、操作人写进数据库,之后做历史查询、权限控制、审计追踪都方便,也能在答辩时展示你对数据库设计的理解。从源码工程角度看,数据库还能让图像算法模块和业务逻辑解耦。

选型上优先推荐SQLite。原因有三个:第一,它是Python标准库的一部分,不需要单独安装数据库服务,老师看代码和演示时跑一次就能通;第二,文件型数据库方便打包提交,不会出现“换台电脑连不上MySQL”的问题;第三,SQLite对并发要求不高,毕设场景完全够用。如果你的项目要同时支持Web端访问或多人操作,再换成MySQL不迟,连接方式我下面会提。

3.2 表结构设计:三张核心表及其字段

设计数据库表是毕业设计里容易被扣分的地方,标准要求是“三范式、主外键清晰、字段类型合理”。针对这个题目,我一般会设计三张表,结构如下:

-- 用户表:存放登录用户的基本信息 CREATE TABLE user ( user_id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, -- 存储哈希值,不存明文 role TEXT DEFAULT 'normal', -- 角色:admin / normal created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 图片记录表:存储载体图片与隐藏图片的文件信息 CREATE TABLE image_record ( record_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, cover_path TEXT NOT NULL, -- 载体图片路径 output_path TEXT, -- 嵌入后图片路径 secret_length INTEGER, -- 秘密信息的字节数 algorithm TEXT DEFAULT 'LSB', -- 使用的算法 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user(user_id) ); -- 嵌入日志表:记录每次嵌入与提取操作 CREATE TABLE embedding_log ( log_id INTEGER PRIMARY KEY AUTOINCREMENT, record_id INTEGER NOT NULL, operation TEXT, -- embed / extract status TEXT DEFAULT 'success', -- success / failed message TEXT, -- 失败原因或备注 FOREIGN KEY (record_id) REFERENCES image_record(record_id) );

这三张表的字段都经过了删减,保留了展示逻辑的关键项。password_hash用哈希值存储,是安全课程里强调过的点;secret_length存秘密信息长度,方便提取时不依赖结束标记,这是给扩展功能留的口子;embedding_log则让项目变成可审计的系统。外键关系在SQLite里默认不强制,但建表时写清楚能让老师看出你的基本功。

3.3 Python中的数据库操作:连接、插入与查询示例

数据库操作在Python里用标准库sqlite3就能完成。注意两点:第一,写完连接代码后要commit(),否则数据不会真正落盘;第二,所有SQL参数必须用?占位符,防止拼接注入。下面是一段典型的插入代码:

import sqlite3 def add_user(username, password_hash): conn = sqlite3.connect('stego.db') cursor = conn.cursor() try: cursor.execute( "INSERT INTO user (username, password_hash) VALUES (?, ?)", (username, password_hash) ) conn.commit() return cursor.lastrowid # 返回新插入记录的ID except sqlite3.IntegrityError: # 用户名重复时触发唯一约束 print('用户名已存在,插入失败') return None finally: conn.close()

这里的cursor.execute第二参数是元组,SQL中的?会依次被替换成usernamepassword_hashlastrowid可以拿到刚插入的主键,但要注意在commit()之后调用。查询历史的代码也类似,只需要把INSERT换成SELECT,并用fetchall()取结果。

我在项目源码里把数据库操作独立成一个database.py模块,只暴露init_db()add_user()insert_record()等方法。这样图像隐写模块只关心图片数据,不关心数据库细节,源码结构清晰,答辩时也好讲。下一章就把这两部分拼起来,组成完整的嵌入与提取流程。

4. 嵌入与提取全流程:Python代码实现、参数调优与排错

4.1 完整流程设计:从输入到落库

把第二章的算法和第三章的数据库拼起来,毕设的核心功能才算完整。我在做这类系统时,会把一条嵌入操作拆成五个步骤:

  1. 接收用户上传的载体图片和秘密信息文件;
  2. 对秘密信息做AES加密(可选),增强安全性;
  3. 调用LSB嵌入函数,生成隐写图片;
  4. 把图片路径、长度、算法等信息写入数据库;
  5. 返回带校验码的结果,前端展示前后对比。

其中第2步是可选的,但它能明显提升课题档次。而且PyCryptodome库安装简单,核心加密代码不到十行。在运行下面的代码前,需要先执行pip install pycryptodome,否则from Crypto.Cipher import AES会直接报错。下面给出加密预处理和完整嵌入控制流的示例。

4.2 核心代码:带加密控制流的完整实现

from Crypto.Cipher import AES import hashlib import os import database # 上一章的自定义模块 def aes_encrypt(data: bytes, key: str) -> bytes: # 用SHA256把任意长度密码转成32字节AES密钥 key_bytes = hashlib.sha256(key.encode('utf-8')).digest() cipher = AES.new(key_bytes, AES.MODE_EAX) nonce = cipher.nonce ciphertext, tag = cipher.encrypt_and_digest(data) # 返回nonce、tag、密文三者的拼接,后续解密时需要分开 return nonce + tag + ciphertext def embed_with_record(cover_path, secret_data, password, user_id): # 对秘密数据加密 encrypted = aes_encrypt(secret_data, password) # 调用第二章的embed_lsb函数,这里假设它在lsb.py里 output_path = 'output_' + os.path.basename(cover_path) byte_len = embed_lsb(cover_path, encrypted, output_path) # 写入数据库 record_id = database.insert_record( user_id=user_id, cover_path=cover_path, output_path=output_path, secret_length=byte_len, algorithm='LSB+AES' ) return record_id, output_path

这段代码的关键参数有三个:data是原始秘密字节,key是用户输入的口令,cover_path是载体图片路径。aes_encryptencrypt_and_digest会同时生成密文和认证标签,tag用于解密时校验数据完整性,防止图像被篡改后提取出错误信息。解密函数要注意按顺序拆开noncetagciphertext,我见过同学把三块数据当成一个整体直接丢给AES,结果解密失败。

提示:noncetagciphertext必须一起保存,否则解密时无法还原原始数据。

4.3 参数调优:图片格式、位平面与嵌入密度

参数调优是毕设答辩里最能让老师觉得你“有工程经验”的部分。第一个参数是载体格式。LSB只能配合PNG、BMP这类无损格式,如果载体是JPEG,请先转成PNG再嵌入,否则嵌入后的图片保存为JPEG会瞬间破坏所有低位数据。第二个参数是选择哪个通道嵌入。我的默认做法是只嵌入蓝色通道,因为人眼对蓝色变化最不敏感,同等嵌入量下PSNR略高;如果你追求容量,可以三个通道全用,但隐藏痕迹也会明显一些。第三个参数是嵌入密度,即每个字节的多少位被用来写信息。上面的代码只用了最低位,容量是1/8;如果你把每个字节的低二位都用来写信息,容量翻倍,但图像质量会下降。下面是一组对比:

嵌入位单像素容量图像失真程度适用场景
1位3比特几乎无感知默认推荐
2位6比特轻微噪点高容量需求
4位12比特可见条纹不推荐作为毕设主方案

实现二位的嵌入只需要把写入和读取的最低有效位数量从1改成2,读取时用pixel & 0x03,写入时先清掉低二位再按位或。我建议在主代码里加一个n_lsb参数,默认2,因为在答辩演示时可以现场调这个参数,让老师看到容量与画质的权衡。

4.4 常见排错:提取失败时的排查顺序

我做这个题目到验收阶段,遇到过最典型的四类问题:

  • 图片格式不对:嵌入后保存为JPG,提取时结束标记找不到。解决:强制用PNG保存。
  • 中文乱码:秘密信息是中文,直接调用encode('utf-8')没问题,但提取后要用同一个编码decode('utf-8')。我见过同学用GBK编码,换台电脑就乱。
  • 位宽不一致:嵌入时按2位写,提取时用1位读,所有低位都错位。解决:把n_lsb写进图片记录表,提取时读取该参数。
  • 数据库写入失败:SQLite的database is locked错误,多半是连接没有关闭或多个线程共用一个连接。解决:每次操作独立连接,加conn.close()

排查时可以按“先看图片是否无损 → 再看位宽参数 → 再看编码 → 最后看数据库字段”的顺序来。这一章的内容已经足以支撑一个完整功能,下一章说说怎么验证成果和写说明文档。

5. 验证隐藏效果与撰写说明文档的四个关键步骤

5.1 用PSNR量化隐藏不可见性

PSNR(峰值信噪比)是衡量隐写后图像质量最常用的指标。它比较原始图和隐写图每个像素的差异,数值越高说明画质损失越小,通常来讲超过30dB肉眼就难以分辨。可以用NumPy算出两图的均方误差,再套公式。三十行内能写完:

import numpy as np from PIL import Image def psnr(img1_path, img2_path): img1 = np.array(Image.open(img1_path).convert('RGB'), dtype=np.float64) img2 = np.array(Image.open(img2_path).convert('RGB'), dtype=np.float64) mse = np.mean((img1 - img2) ** 2) if mse == 0: return float('inf') return 10 * np.log10(255**2 / mse)

在文档中把原始图、隐写图和直方图对比放在一起,再给出PSNR数值,就能直观证明隐藏的效果。用该函数对比同一张图在1位、2位嵌入下的PSNR,你会看到数值从约45dB降到约35dB,这个数据可以直接写进测试报告。

5.2 鲁棒性测试与常见坑

毕设里另一个加分的验证是做鲁棒性测试:把隐写图做旋转、加噪声、重新缩放,然后看能不能提取出信息。LSB对这类操作几乎零鲁棒性,但你可以把测试结果作为“算法局限性”写进文档,然后提出改进方向(比如换成DCT)。注意,做测试时不要用手机拍屏幕,那种照片的光学畸变和摩尔纹不属于常规信号处理范围,做了只会得到一堆没有意义的失败数据。

5.3 说明文档的结构建议

说明文档是毕设评分的重要部分,我推荐的顺序是:需求分析(强调系统要解决什么问题)、总体设计(模块划分和数据库ER图)、详细设计(LSB嵌入流程、AES加解密流程)、测试报告(功能测试、性能测试、鲁棒性测试)、使用手册(从python安装、依赖安装到启动命令)。其中“使用手册”部分要写出实际可复制的命令,比如python main.py --mode embed --cover cover.png --secret secret.txt --password 123456,而不是只写“运行程序”。

5.4 答辩演示时的一个技巧

最后留一个具体技巧:答辩时准备一个debug.shrun_test.py,用三百行的自动化脚本把“嵌入→提取→比对→显示PSNR”跑通,现场演示时不用点鼠标,一句python run_test.py就能输出完整日志。这比临时打开控制台敲命令要稳得多,也能体现你对工程质量的理解。把脚本和源码一起提交到Git仓库,说明文档里附上运行结果截图,整个毕设的可信度就会上一个台阶。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 15:12:23

Cal.com 集成 Mirotalk 视频会议应用(mirotalk_video)接入指南

Cal.com 集成 Mirotalk 视频会议应用(mirotalk_video)接入指南 【免费下载链接】cal.diy Scheduling infrastructure for absolutely everyone. 项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy Mirotalk 是一个基于 WebRTC 的免费实时…

作者头像 李华