:完整源碼與部署避坑指南)
簡(jiǎn)介一套基于 Python 與 Django 框架實(shí)現(xiàn)的圖像信息隱藏技術(shù)畢業(yè)設(shè)計(jì)/課程設(shè)計(jì)源碼包面向計(jì)算機(jī)相關(guān)專業(yè)學(xué)生適用于畢業(yè)設(shè)計(jì)、課程設(shè)計(jì)或綜合實(shí)訓(xùn)也適合研究信息隱藏、數(shù)字水印與 Web 開(kāi)發(fā)結(jié)合的開(kāi)發(fā)者參考。壓縮包約 38.02MB文件總數(shù)與類型明細(xì)暫未單獨(dú)列出從項(xiàng)目描述看主體包含 Django 工程代碼、部署說(shuō)明與項(xiàng)目說(shuō)明文檔可支持在本地環(huán)境復(fù)現(xiàn)系統(tǒng)并跟蹤整體實(shí)現(xiàn)脈絡(luò)。資源核心是秘密信息嵌入與提取的圖像處理流程借助 PIL、OpenCV 等庫(kù)進(jìn)行像素級(jí)處理同時(shí)使用 Django 構(gòu)建上傳、處理與展示界面并依靠 ORM 完成圖像及相關(guān)數(shù)據(jù)的存儲(chǔ)前后端與數(shù)據(jù)庫(kù)形成完整閉環(huán)。已有 66 人瀏覽學(xué)習(xí)讀者可借此了解整份畢設(shè)源碼的目錄結(jié)構(gòu)、部署步驟、隱寫算法工程化落地要點(diǎn)以及 Django 項(xiàng)目從模型到視圖的集成方式。1. 圖像信息隱藏畢設(shè)一份能跑的DjangoLSB完整源碼圖像信息隱藏這個(gè)畢設(shè)方向每年都有人做但流傳出來(lái)的源碼要么跑不起來(lái)要么缺東少西。這份項(xiàng)目不一樣的地方在于它把LSB位平面隱寫算法、Django Web前后端和SQLite數(shù)據(jù)庫(kù)串成了完整閉環(huán)上傳載體圖、輸入秘密文本、生成肉眼無(wú)差別的隱寫圖再通過(guò)提取端把原文完整撈回來(lái)。別看它是個(gè)課程設(shè)計(jì)級(jí)別的項(xiàng)目里面圖像處理、Web框架、ORM模型三塊都有實(shí)際覆蓋非常適合計(jì)算機(jī)、網(wǎng)絡(luò)空間安全、軟件工程專業(yè)拿來(lái)當(dāng)畢設(shè)底子也適合剛接觸信息隱藏的開(kāi)發(fā)者照著復(fù)現(xiàn)一遍。下面對(duì)著源碼按算法、工程、部署、避坑的順序拆開(kāi)講。2. LSB嵌入與提取從位平面到可復(fù)現(xiàn)的Python代碼2.1 為什么選LSB改最低位肉眼真的看不出來(lái)一個(gè)像素在RGB模型里由紅、綠、藍(lán)三個(gè)通道組成每個(gè)通道占一個(gè)字節(jié)取值0到255換算成二進(jìn)制就是8位。左邊的高位決定了像素亮度和色彩的主體右邊最低位只影響1/255的亮度差。把秘密信息拆成單個(gè)比特逐個(gè)替換掉每個(gè)通道的最低位后像素變化最多只有±1人眼在這點(diǎn)差異面前基本是“瞎子”。這并不是玄學(xué)而是亮度差低于人眼識(shí)別閾值的數(shù)學(xué)結(jié)論也是LSBLeast Significant Bit隱寫成為畢設(shè)最常見(jiàn)方案的根本原因。項(xiàng)目正文里提到的PIL和OpenCV在這套流程中我一般傾向用PIL來(lái)做。原因很直接PIL讀圖像、取像素、寫像素的接口足夠簡(jiǎn)單幾行代碼就能拿下“讀-改-存”三段流程OpenCV更適合后面要做灰度變換、頻域分析或圖像預(yù)處理的場(chǎng)景。畢設(shè)核心是信息嵌入提取用PIL能少踩不少格式轉(zhuǎn)換的坑。2.2 嵌入端實(shí)現(xiàn)把文本拆成比特塞進(jìn)像素最低位from PIL import Image def lsb_embed(carrier_path, secret_text, output_path): img Image.open(carrier_path).convert(RGB) # 統(tǒng)一轉(zhuǎn)RGB兼容PNG等格式 pixels list(img.getdata()) # getdata()返回每個(gè)像素的RGB三元組 # 16位長(zhǎng)度頭 UTF-8字節(jié)流長(zhǎng)度頭用于提取端知道要讀多少字節(jié) secret_bytes secret_text.encode(utf-8) length_bits format(len(secret_bytes), 016b) secret_bits .join(format(b, 08b) for b in secret_bytes) full_bits length_bits secret_bits # 每個(gè)像素有3個(gè)通道可用bit數(shù) 像素總數(shù) * 3 if len(full_bits) len(pixels) * 3: raise ValueError(秘密信息過(guò)長(zhǎng)超出載體容量) new_pixels [] bit_index 0 for pixel in pixels: r, g, b pixel[:3] # 0xFE 把最低位清成0再或上秘密bit實(shí)現(xiàn)單比特替換 if bit_index len(full_bits): r (r 0xFE) | int(full_bits[bit_index]) bit_index 1 if bit_index len(full_bits): g (g 0xFE) | int(full_bits[bit_index]) bit_index 1 if bit_index len(full_bits): b (b 0xFE) | int(full_bits[bit_index]) bit_index 1 new_pixels.append((r, g, b)) new_img Image.new(RGB, img.size) new_img.putdata(new_pixels) new_img.save(output_path)邏輯說(shuō)明carrier_path是上傳的原始載體圖secret_text是要藏的文本output_path是生成的隱寫圖。先把文本按UTF-8編碼成字節(jié)再把每個(gè)字節(jié)展開(kāi)成8位二進(jìn)制串前面拼一個(gè)16位長(zhǎng)度頭提取端先讀這個(gè)頭才知道后面要取多少比特。容量判斷是關(guān)鍵一個(gè)像素三個(gè)通道就是三個(gè)比特位超了直接拋異常而不是靜默截?cái)噙@點(diǎn)能省掉后面一大半排查時(shí)間。參數(shù)說(shuō)明format(len(secret_bytes), 016b) 強(qiáng)制輸出16位定長(zhǎng)二進(jìn)制長(zhǎng)度范圍0到65535字節(jié)對(duì)畢設(shè)場(chǎng)景完全夠用 0xFE 是保留高7位、清最低位的標(biāo)準(zhǔn)位運(yùn)算寫法。最終保存的隱寫圖要選PNG這類無(wú)損格式這是LSB能否提取成功的先決條件。2.3 提取端實(shí)現(xiàn)取最低位拼回流再解碼from PIL import Image def lsb_extract(stego_path): img Image.open(stego_path).convert(RGB) pixels list(img.getdata()) # 逐通道收集最低位組成完整比特流 bits [] for pixel in pixels: for channel in pixel[:3]: bits.append(channel 1) # 先解析16位長(zhǎng)度頭非法長(zhǎng)度直接報(bào)錯(cuò) length int(.join(str(b) for b in bits[:16]), 2) if length 0 or length len(pixels) * 3 // 8: raise ValueError(提取失敗長(zhǎng)度頭不合法或圖像不是有效載體) # 按8位一組拼字節(jié)再?gòu)淖止?jié)解碼回UTF-8文本 secret_bytes bytearray() for i in range(16, 16 length * 8, 8): byte 0 for j in range(8): byte (byte 1) | bits[i j] secret_bytes.append(byte) return secret_bytes.decode(utf-8)邏輯說(shuō)明提取是嵌入的逆過(guò)程channel 1 取每個(gè)通道的最低位按順序收集成比特流。前16位轉(zhuǎn)成十進(jìn)制得到length再按8位一組切字節(jié)。這里對(duì)length做了合法性校驗(yàn)避免拿一張沒(méi)藏過(guò)信息的普通圖片硬解碼時(shí)得到超長(zhǎng)內(nèi)容或亂碼報(bào)錯(cuò)。decode(utf-8) 必須和嵌入端的 encode(utf-8) 配對(duì)英文文本用什么編碼差別不大中文則必須統(tǒng)一。參數(shù)說(shuō)明stego_path 是待提取的隱寫圖路徑range(16, 16 length * 8, 8) 確保只讀長(zhǎng)度頭聲明范圍內(nèi)的比特不越界。至此嵌入和提取兩個(gè)函數(shù)形成對(duì)稱閉環(huán)項(xiàng)目的核心算法部分就跑通了。3. Django包成Web系統(tǒng)ORM模型、視圖鏈路與URL路由3.1 models.py數(shù)據(jù)模型一張表記錄上傳載體與嵌入結(jié)果Django的ORM把數(shù)據(jù)庫(kù)表映射成Python類這張表要同時(shí)承載三件事記錄上傳的載體圖、記錄生成的隱寫圖、記錄用戶輸入的秘密文本。核心字段定義如下from django.db import models class ImageRecord(models.Model): carrier models.ImageField(upload_tocarrier/) # 用戶上傳的載體圖 stego models.ImageField(upload_tostego/, blankTrue, nullTrue) # 生成的隱寫圖 secret_text models.TextField(blankTrue) # 嵌入的秘密信息 created_at models.DateTimeField(auto_now_addTrue) # 操作時(shí)間 remark models.CharField(max_length255, blankTrue) # 備用備注字段參數(shù)說(shuō)明carrier 用 ImageField 而不是 FileField是為了讓 Django 在 admin 后臺(tái)直接顯示圖片預(yù)覽upload_to 參數(shù)決定文件落到 MEDIA_ROOT 下的哪個(gè)子目錄。stego 字段允許為空因?yàn)橛涗泟倓?chuàng)建時(shí)隱寫圖還沒(méi)生成要在視圖函數(shù)里跑完算法再回填。secret_text 用 TextField 存長(zhǎng)文本用戶可能輸入幾百字甚至一段代碼。一個(gè)容易忽略的點(diǎn)ImageField 依賴 Pillow 庫(kù)requirements.txt 里必須帶上 pillow否則遷移時(shí)會(huì)報(bào)“不能初始化 ImageField”。很多第一次跑畢設(shè)項(xiàng)目的人在 makemigrations 那一步就卡住十有八九是漏了這一步。3.2 views.py鏈路上傳、嵌入、提取如何串聯(lián)算法函數(shù)寫好后需要通過(guò)視圖把HTTP請(qǐng)求、算法調(diào)用、文件保存串起來(lái)。核心視圖分成兩條鏈路嵌入和提取。import os from django.conf import settings from django.shortcuts import render from .models import ImageRecord from .lsb import lsb_embed, lsb_extract def embed_view(request): if request.method POST: # 把上傳文件和表單數(shù)據(jù)一起入庫(kù) record ImageRecord( carrierrequest.FILES[carrier], secret_textrequest.POST[secret_text] ) record.save() # 調(diào)用嵌入算法隱寫圖命名跟記錄id掛鉤避免覆蓋 carrier_path record.carrier.path stego_path os.path.join(settings.MEDIA_ROOT, stego, fstego_{record.id}.png) lsb_embed(carrier_path, record.secret_text, stego_path) # 回填隱寫圖字段再保存一次 record.stego fstego/stego_{record.id}.png record.save() return render(request, result.html, {record: record}) return render(request, embed.html) def extract_view(request): if request.method POST: # 先落盤再傳路徑給算法 stego_file request.FILES[stego] save_path os.path.join(settings.MEDIA_ROOT, uploads, stego_file.name) with open(save_path, wb) as f: for chunk in stego_file.chunks(): f.write(chunk) secret lsb_extract(save_path) return render(request, extract_result.html, {secret: secret}) return render(request, extract.html)邏輯說(shuō)明embed_view 里先 save() 拿到主鍵 id隱寫文件名用 stego_{record.id}.png同一條記錄對(duì)應(yīng)一個(gè)確定文件名重復(fù)嵌入也不會(huì)互相覆蓋。算法跑完再把 stego 字段更新回去第二次 save() 只更新這一條記錄。extract_view 這邊把上傳文件先寫入 MEDIA_ROOT/uploads 臨時(shí)目錄這種落盤再處理的習(xí)慣比直接在內(nèi)存里轉(zhuǎn)來(lái)轉(zhuǎn)去要穩(wěn)得多排錯(cuò)時(shí)也能直接看到上傳的文件內(nèi)容。參數(shù)說(shuō)明request.FILES 是 Django 處理 multipart/form-data 上傳的入口必須和前端表單的 enctypemultipart/form-data 對(duì)應(yīng)否則 request.FILES 取出來(lái)是空的settings.MEDIA_ROOT 必須在 settings.py 里定義通常配成 os.path.join(BASE_DIR, media)chunks() 是 Django 對(duì)大文件分塊讀寫的推薦接口避免一次性把大文件讀進(jìn)內(nèi)存。3.3 urls.py路由與模板渲染的對(duì)應(yīng)關(guān)系Django 的路由表決定每個(gè) URL 交給哪個(gè)視圖函數(shù)。這個(gè)項(xiàng)目至少需要兩個(gè)頁(yè)面一個(gè)嵌入頁(yè)一個(gè)提取頁(yè)。路由配置如下from django.urls import path from . import views urlpatterns [ path(embed/, views.embed_view, nameembed), path(extract/, views.extract_view, nameextract), ]參數(shù)說(shuō)明nameembed 用于模板里的 {% url embed %} 反向解析改名時(shí)模板里的引用也要同步改這是新手最容易忽略的關(guān)聯(lián)關(guān)系。對(duì)應(yīng)的模板文件放在 app 目錄下的 templates 文件夾里嵌入頁(yè)表單的核心寫法是里面一個(gè)和一個(gè)name 的值必須和視圖里 request.FILES[carrier]、request.POST[secret_text] 完全一致錯(cuò)一個(gè)字母就取不到數(shù)據(jù)。/p p模板渲染那層不復(fù)雜就是寫完表單交后臺(tái)后臺(tái)返回 result.html 并帶上 record 對(duì)象頁(yè)面里用 {?{ record.stego.url }} 顯示生成結(jié)果。把 urls、views、templates 三者對(duì)上整個(gè) Web 框架就完整跑通了。/p h24. 部署與復(fù)現(xiàn)從zip解壓到runserver跑通的完整路徑/h2 h34.1 環(huán)境準(zhǔn)備Python版本、虛擬環(huán)境與依賴安裝/h3 p拿到壓縮包先解壓項(xiàng)目文件夾內(nèi)通常包含 xiangmu 主代碼目錄、說(shuō)明文檔、部署說(shuō)明這幾類內(nèi)容。常見(jiàn)結(jié)構(gòu)如下/p table thead tr th解壓后常見(jiàn)內(nèi)容/th th作用/th /tr /thead tbody tr tdxiangmu//td tdDjango項(xiàng)目主目錄含manage.py與app代碼/td /tr tr td說(shuō)明文檔/td td項(xiàng)目設(shè)計(jì)目標(biāo)、模塊分析、使用說(shuō)明/td /tr tr td部署說(shuō)明.zip/td td環(huán)境安裝、依賴配置、啟動(dòng)步驟/td /tr /tbody /table p別急著直接雙擊 manage.py先按順序做環(huán)境準(zhǔn)備。這個(gè)項(xiàng)目基于 Django建議用 Python 3.8 到 3.10太新的 Python 版本碰到老項(xiàng)目的三方依賴可能出兼容問(wèn)題。依賴安裝的標(biāo)準(zhǔn)做法是先建虛擬環(huán)境再裝依賴/p precode classlanguage-bash# 進(jìn)入項(xiàng)目主目錄 cd xiangmu # 創(chuàng)建虛擬環(huán)境 python -m venv venv # Windows激活虛擬環(huán)境 venv\Scripts\activate # Linux / macOS激活虛擬環(huán)境 source venv/bin/activate # 安裝依賴 pip install -r requirements.txt /code/pre p邏輯說(shuō)明虛擬環(huán)境的作用是把項(xiàng)目依賴和系統(tǒng) Python 隔離避免多個(gè)項(xiàng)目互相污染。requirements.txt 里一般包含 django、pillow 以及可能用到的其他庫(kù)django 負(fù)責(zé) Webpillow 負(fù)責(zé)圖像讀寫這兩個(gè)是標(biāo)配。如果部署說(shuō)明里另外寫了 mysqlclient 或 pymysql說(shuō)明數(shù)據(jù)庫(kù)用了 MySQL本地想省事可以在 settings.py 里把 DATABASES 改成 SQLite 配置不影響演示功能。/p p參數(shù)說(shuō)明python -m venv 依賴 Python3 自帶模塊不需要額外安裝激活后命令行前綴出現(xiàn) (venv) 說(shuō)明虛擬環(huán)境生效。Windows 下如果用 PowerShellvenv\Scripts\activate 可能因執(zhí)行策略報(bào)錯(cuò)臨時(shí)放開(kāi)策略或改用 venv\Scripts\activate.bat 都能解決。/p h34.2 數(shù)據(jù)庫(kù)初始化遷移、超級(jí)用戶與媒體目錄/h3 p數(shù)據(jù)庫(kù)是 Django 繞不開(kāi)的環(huán)節(jié)。這個(gè)項(xiàng)目用 ORM 操作 SQLite 或 MySQL第一次運(yùn)行前必須建表/p precode classlanguage-bash# 生成遷移文件把models.py變成數(shù)據(jù)庫(kù)操作 python manage.py makemigrations # 真正執(zhí)行建表 python manage.py migrate # 創(chuàng)建admin超級(jí)用戶后臺(tái)用 python manage.py createsuperuser /code/pre p邏輯說(shuō)明makemigrations 掃描 app 里的 models.py 生成遷移腳本migrate 執(zhí)行腳本建表。很多第一次跑的人只跑 runserver 不跑 migrate訪問(wèn)頁(yè)面就報(bào) no such table這是 Django 項(xiàng)目的頭號(hào)翻車點(diǎn)。createsuperuser 創(chuàng)建的賬號(hào)用于登錄 admin 管理后臺(tái)答辯時(shí)常被問(wèn)到順手建一個(gè)不會(huì)錯(cuò)。/p p參數(shù)說(shuō)明如果 app 沒(méi)被注冊(cè)進(jìn) INSTALLED_APPSmakemigrations 會(huì)提示沒(méi)有改動(dòng)這時(shí)要先在 settings.py 里把 app 名加進(jìn)去如果 settings.py 里配置了 MEDIA_ROOT還需要手動(dòng)建 media 目錄Django 不會(huì)自動(dòng)創(chuàng)建命令行 mkdir media 就能解決否則上傳文件時(shí) open() 會(huì)報(bào)路徑不存在。/p h34.3 啟動(dòng)服務(wù)完整跑一次嵌入提取流程/h3 p環(huán)境、數(shù)據(jù)庫(kù)、媒體目錄都就緒后啟動(dòng)開(kāi)發(fā)服務(wù)器/p precode classlanguage-bashpython manage.py runserver 0.0.0.0:8000 /code/pre p瀏覽器打開(kāi) http://127.0.0.1:8000/embed/ 選一張 PNG 載體圖輸入“hello 你好”點(diǎn)嵌入頁(yè)面展示生成的隱寫圖。再打開(kāi)提取頁(yè)上傳這張隱寫圖提取結(jié)果應(yīng)該和輸入完全一致。這一輪測(cè)試下來(lái)算法、Web、ORM、靜態(tài)文件四條鏈路就全部驗(yàn)證到了。/p p參數(shù)說(shuō)明0.0.0.0:8000 表示監(jiān)聽(tīng)所有網(wǎng)卡局域網(wǎng)內(nèi)其他機(jī)器可以用開(kāi)發(fā)機(jī) IP 加 8000 端口訪問(wèn)只在本機(jī)調(diào)試時(shí)直接runserver即可。生產(chǎn)部署想上 Nginx 加 uwsgi部署說(shuō)明里如果有對(duì)應(yīng)章節(jié)可以照做但畢設(shè)演示階段 runserver 已經(jīng)足夠。/p h25. 避坑與排查圖像隱寫項(xiàng)目最常翻車的四個(gè)環(huán)節(jié)/h2 h35.1 嵌入后另存成JPG提取出來(lái)全是亂碼/h3 p現(xiàn)象隱寫圖保存時(shí)被轉(zhuǎn)成了 JPG 格式再喂給提取端解出來(lái)的內(nèi)容完全不可讀。/p p原因LSB 依賴像素最低位的精確值JPG 是有損壓縮格式編碼過(guò)程中量化表會(huì)把低位數(shù)據(jù)改掉嵌入的信息被壓縮算法破壞了。這跟算法本身沒(méi)關(guān)系是載體格式選錯(cuò)了。/p p解決載體圖和隱寫圖的保存路徑統(tǒng)一限定為 PNG、BMP 這類無(wú)損格式。前端上傳控件限制 accept.png,.bmp后端視圖里再用文件后綴做一道校驗(yàn)雙重保險(xiǎn)。如果業(yè)務(wù)上必須用 JPG就要換成第 6 章講的 DCT 頻域方案那是另一套思路。/p h35.2 秘密信息是中文提取時(shí)直接UnicodeDecodeError/h3 p現(xiàn)象嵌英文一切正常換中文后要么提取結(jié)果亂碼要么拋 UnicodeDecodeError。/p p原因嵌入端用的編碼和解碼端不一致或者用 ASCII 這類單字節(jié)編碼去處理中文。中文字符在 UTF-8 里通常是 3 個(gè)字節(jié)被拆成 24 個(gè) bit 嵌入提取時(shí)只要有一端編碼不對(duì)字節(jié)流就拼不回合法的中文字符。/p p解決嵌入端固定用 secret_text.encode(utf-8)提取端固定用 bytes.decode(utf-8)兩個(gè)函數(shù)放在一起對(duì)照檢查。這是血淚經(jīng)驗(yàn)換來(lái)的提醒我見(jiàn)過(guò)好幾次學(xué)生答辯現(xiàn)場(chǎng)演示中文提取翻車全是編碼不一致導(dǎo)致的。/p h35.3 秘密信息一長(zhǎng)嵌入就報(bào)容量不足/h3 p現(xiàn)象輸入幾百字文本沒(méi)問(wèn)題換成幾千字或嵌入圖片時(shí)lsb_embed 直接拋出 ValueError。/p p原因每個(gè)像素只有 3 個(gè) bit 可用容量上限是像素總數(shù)乘 3 再除以 8單位才是字節(jié)。一張 1024x768 的圖理論容量約 29 萬(wàn)字節(jié)看起來(lái)很大但只要秘密信息經(jīng)過(guò)編碼或壓縮處理體積立刻膨脹超出載體容量。/p p解決嵌入前先算容量前端用 JS 按字節(jié)數(shù)做預(yù)估提示后端保留異常拋出邏輯再加一個(gè) try...except 把錯(cuò)誤信息換成“秘密信息過(guò)長(zhǎng)請(qǐng)更換更大載體或壓縮內(nèi)容”。硬截?cái)嗍亲畈畹奶幚矸绞綄幙勺層脩魮Q圖也不要生成一張?zhí)崛∈〉碾[寫圖。/p h35.4 首次部署訪問(wèn)頁(yè)面就500日志顯示no such table/h3 p現(xiàn)象migrate 之后訪問(wèn) embed 頁(yè)面或提交表單Django 返回 500日志里帶著 Table doesnt exist。/p p原因models.py 建好了但 makemigrations 或 migrate 沒(méi)有成功執(zhí)行或者是后來(lái)改過(guò)模型字段舊遷移文件對(duì)應(yīng)的表和新字段對(duì)不上。/p p解決重新跑一遍 makemigrations 和 migrate確認(rèn)輸出里有 Applying 開(kāi)頭的日志。如果反復(fù)不生效檢查 settings.py 里 INSTALLED_APPS 有沒(méi)有包含項(xiàng)目 app 名把 xxx.apps.XxxConfig 加進(jìn)去再遷移。這個(gè)坑在 Django 新手里出現(xiàn)頻率極高基本屬于必修踩坑點(diǎn)。/p h26. 從畢設(shè)到能演示的實(shí)用工具校驗(yàn)位與DCT域進(jìn)階改法/h2 h36.1 加魔數(shù)校驗(yàn)位提取前先判斷“這張圖是不是被藏過(guò)”/h3 p基礎(chǔ)版提取端遇到普通圖片會(huì)硬解碼得到亂碼甚至拋異常這在答辯演示時(shí)很尷尬。一個(gè)低成本改進(jìn)是加魔數(shù)Magic Number校驗(yàn)嵌入前在信息流前面拼一段固定字節(jié)提取時(shí)先檢查這段字節(jié)是否匹配不匹配就直接提示“不是有效載體”。/p precode classlanguage-pythonMAGIC bINFMAGIC # 8字節(jié)固定魔數(shù)可自行更換 def build_bits(secret_text): payload MAGIC secret_text.encode(utf-8) length_bits format(len(payload), 016b) secret_bits .join(format(b, 08b) for b in payload) return length_bits secret_bits def parse_bits(bits): length int(.join(str(b) for b in bits[:16]), 2) payload bytearray() for i in range(16, 16 length * 8, 8): byte 0 for j in range(8): byte (byte 1) | bits[i j] payload.append(byte) if not bytes(payload[:len(MAGIC)]) MAGIC: raise ValueError(不是有效載體) return bytes(payload[len(MAGIC):]).decode(utf-8) /code/pre p參數(shù)說(shuō)明MAGIC 用 8 字節(jié)固定值payload 長(zhǎng)度上限從 65535 字節(jié)降到 65527 字節(jié)對(duì)畢設(shè)文本場(chǎng)景沒(méi)有實(shí)際影響。提取時(shí)先比對(duì)魔數(shù)比對(duì)失敗立即報(bào)錯(cuò)這樣普通圖片不會(huì)再被硬解碼成亂碼字符串頁(yè)面交互也體面得多。/p h36.2 魯棒性提升LSB空間域換成DCT頻域的思路/h3 pLSB 的弱點(diǎn)是扛不住有損壓縮和輕微幾何攻擊論文里想往上走一步最常見(jiàn)的延展方向是 DCT 頻域隱寫類似 JSteg 的思路。做法是把圖像切成 8x8 像素塊每塊做離散余弦變換得到頻域系數(shù)在中頻系數(shù)的最低有效位嵌入信息。中頻系數(shù)受壓縮量化影響比高頻小嵌入后的隱寫圖就算被存成 JPG提取成功率也比空間域 LSB 高一個(gè)量級(jí)代價(jià)是容量變小、代碼復(fù)雜度明顯上升。/p p畢設(shè)階段如果只求穩(wěn)加個(gè)魔數(shù)校驗(yàn)已經(jīng)夠了DCT 方案可以作為論文“未來(lái)工作”那節(jié)的素材如果指導(dǎo)老師明確要求抗 JPEG 壓縮再考慮這一步。從那以后我每次做隱寫項(xiàng)目都強(qiáng)制把載體格式限定為 PNG、嵌入前校驗(yàn)長(zhǎng)度和魔數(shù)、上傳接口必測(cè)中文文本三件事做完才敢說(shuō)這套代碼能穩(wěn)定復(fù)現(xiàn)。希望幫到你。/p p a hrefhttps://download.csdn.net/download/luoluoal/88442535 stylecolor:#ec7500;font-size:14px; 本文還有配套的精品資源點(diǎn)擊獲取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p