Python 3画像隠写術

34303 ワード

Python 3画像隠写術
一、実験概要
wikipediaの隠写術についての紹介:
隠写術は情報隠蔽に関する技術と科学であり、情報隠蔽とは、予想される受信者以外の誰にも情報の伝達イベントや情報の内容を知られないことを指す.隠写術の英語はSteganographyと呼ばれ、トリトミウスの暗号学と隠写術を描いた著作Steganographiaに由来し、ギリシャ語に由来し、「隠秘書」を意味する.
1.1. インテリジェントポイント
Pillowモジュール最小有効ビットLambda式再帰UTF-8符号化1.2. 効果の表示
「施法」の前後の画像は肉眼では区別がつかないが、画像にはデータが隠されている.
二、実験手順
2.1. じっけんげんり
wikipediaの説明を引用します.
キャリアファイル(cover file)は、秘密ファイルのサイズ(データ含有量を指し、ビットで計算)が大きいほど、後者を隠すことが容易になる.
このため、デジタル画像(大量のデータを含む)は、インターネットや他のメディア上でメッセージを隠すために広く用いられている.この方法の使用の広さは調べられない.例えば、1つの24ビットのビットマップの各画素の3つの色成分(赤、緑、青)は、それぞれ8ビットで表される.青だけを考えると、深さと浅さの異なる青を表す2^8の異なる数値があります.11111111と11111110の2つの値で表される青のように、人の目はほとんど区別できない.従って、この最低有効ビットは、色以外の情報を格納するために使用することができ、ある程度は検出できない.赤と緑を同様に操作すれば、差の少ない3画素に1バイトの情報を格納することができる.
より正式には、暗記された情報を検出することが困難になる、すなわち、「キャリア」(すなわち、元の信号)に対する「ペイロード」(隠蔽される必要がある信号)の変調がキャリアに及ぼす影響(理想的には統計的にも)無視できることを保証する.すなわち、この変更は、キャリア内のノイズと区別できないはずである.
(情報論の観点から、チャネルの容量は、伝送の「表面的」よりも大きくなければならない.の信号の需要.これをチャネルの冗長性と呼ぶ.1枚のデジタル画像について、この冗長性は撮像ユニットのノイズである可能性がある.デジタルオーディオの場合、録音または増幅装置によって発生するノイズである可能性があります.シミュレーション増幅レベルを有する任意のシステムには、いわゆる熱ノイズ(または「1/f」ノイズ)があり、これは隠蔽として使用することができる.また、JPEGなどの損失圧縮技術は、解凍後のデータに誤差を導入し、これらの誤差を隠写術の用途に利用することも可能である.
暗記術はデジタル透かしとしても使用でき、ここではメッセージ(しばしば識別子)が画像に隠され、ソースが追跡または検証されるようにする.
要するに,本実験は,画像の4色成分(rgba)の最低有効ビット(英語:Least Significant Bit,lsb)を用いて情報を隠す(本実験では文字を隠す)ことである.
2.2. パッケージのインストール
本実験ではpillowというモジュールを用いて,それをインストールする前にソースを更新した.
$ sudo apt-get update

まず、現在の実験棟の環境でpython 3コマンドで使用されているpythonバージョンは3.5ですが、ソースにはpython 3がありません.5-dev、Pillowのインストールエラーが発生します.python 3コマンドで使用するpythonバージョンを3.4に切り替え、python 3-devとpython 3-setuptoolsをインストールする必要があります.
$ sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.4 70 --slave /usr/bin/python3m python3m /usr/bin/python3.4m
$ sudo apt-get install python3-dev python3-setuptools

次にPillow依存パッケージをインストールします
$ sudo apt-get install libtiff5-dev libjpeg8-dev zlib1g-dev \
    libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk

最後にPillowをインストール:
$ sudo pip3 install Pillow

2.3. プログラム実装
まずPillowモジュールをインポートします.
from PIL import Image

2.3.1. エンコーディング
まず,ベクターとして用いられるピクチャオブジェクトと隠す必要がある文字列の2つのパラメータを有する隠蔽情報をピクチャに符号化する関数encodeDataInImage()を設計した.つまり、このように呼び出すことができます.
encodeDataInImage(Image.open("steganographia.png"), '    ,Hello world!')
encodeDataInImage()の関数は次のとおりです.
def encodeDataInImage(image, data):
    evenImage = makeImageEven(image)  #          0      
    binary = ''.join(map(constLenBin,bytearray(data, 'utf-8'))) #                    
    if len(binary) > len(image.getdata()) * 4:  #            ,     
        raise Exception("Error: Can't encode more than " + len(evenImage.getdata()) * 4 + " bits in this image. ")
    encodedPixels = [(r+int(binary[index*4+0]),g+int(binary[index*4+1]),b+int(binary[index*4+2]),t+int(binary[index*4+3])) if index*4 < len(binary) else (r,g,b,t) for index,(r,g,b,t) in enumerate(list(evenImage.getdata()))] #   binary                 
    encodedImage = Image.new(evenImage.mode, evenImage.size)  #               
    encodedImage.putdata(encodedPixels)  #         
    return encodedImage
makeImageEven()関数の実装は次のとおりです.
def makeImageEven(image):
    pixels = list(image.getdata())  #          : [(r,g,b,t),(r,g,b,t)...]
    evenPixels = [(r>>1<<1,g>>1<<1,b>>1<<1,t>>1<<1) for [r,g,b,t] in pixels]  #         (      )
    evenImage = Image.new(image.mode, image.size)  #              
    evenImage.putdata(evenPixels)  #              
    return evenImage

関連関数のドキュメントリンク:
Image.getdata()PIL.Image.new()PIL.Image.Image.putdata() encodeDataInImage()のうち、bytearray()は文字列を整数値シーケンス(数値範囲は0から2^8-1)に変換し、数値シーケンスは文字列のバイトデータから変換され、以下の図に示す.
utf-8符号化の中国語文字は1文字で3バイトを占め、4文字で3文字を占める×4=12バイトで、12個の数字があります.(右下で中国語入力方式に切り替えて中国語入力できるようになりました)
次いで、map(constLenBin,bytearray(data, 'utf-8'))は、数値シーケンスの各値にconstLenBin()関数を適用し、10進数数値シーケンスをバイナリ文字列シーケンスに変換する.
def constLenBin(int):
    binary = "0"*(8-(len(bin(int))-2))+bin(int).replace('0b','')  #    bin()             '0b',       '0'          8
    return binary

ここでbin()の役割は、int値をバイナリ文字列に変換することです.詳細は、次の項を参照してください.https://docs.python.org/3/library/functions.html#bin
2.3.2. デコードdecodeImage()は、ピクチャ復号後の非表示文字を返し、ピクチャオブジェクトパラメータを受け取る.
def decodeImage(image):
    pixels = list(image.getdata())  #       
    binary = ''.join([str(int(r>>1<<1!=r))+str(int(g>>1<<1!=g))+str(int(b>>1<<1!=b))+str(int(t>>1<<1!=t)) for (r,g,b,t) in pixels])  #                 
    #           
    locationDoubleNull = binary.find('0000000000000000')
    endIndex = locationDoubleNull+(8-(locationDoubleNull % 8)) if locationDoubleNull%8 != 0 else locationDoubleNull
    data = binaryToString(binary[0:endIndex])
    return data

str.find()
データのカットオフに使用される文字列'0000000000000000'を見つけるのは興味深い.彼の長さは直感的な8ではなく16である.データを含む2つのバイトの接触部分は8つの0がある可能性があるからだ.binaryToString()関数は、抽出されたバイナリ文字列を非表示のテキストに変換します.
def binaryToString(binary):
    index = 0
    string = []
    rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i > 1 else '') if x else ''
    # rec = lambda x, i: x and (x[2:8] + (i > 1 and rec(x[8:], i-1) or '')) or ''
    fun = lambda x, i: x[i+1:8] + rec(x[8:], i-1)
    while index + 1 < len(binary):
        chartype = binary[index:].index('0')
        length = chartype*8 if chartype else 8
        string.append(chr(int(fun(binary[index:index+length],chartype),2)))
        index += length
    return ''.join(string)

これを理解するには、まずUTF-8の符号化方法を理解しなければならない.wikipediaでUTF-8の符号化を理解することができる.https://zh.wikipedia.org/wiki/UTF-8
UTF-8はUNICODの長さを変える符号化表現で、つまり1つの文字列の中で、異なる文字が占めるバイト数が必ずしも同じとは限らないので、私たちの仕事に少し複雑さをもたらしました.もし私たちが中国語をサポートするなら(それはくだらない話ではありませんか).
コードポイントのビット数
コード点から値
コードポイントしゅうち
バイトシーケンス
Byte 1
Byte 2
Byte 3
Byte 4
Byte 5
Byte 6
7
U+0000
U+007F
1 0xxxxxxx
 
 
 
 
 
11
U+0080
U+07FF
2 110xxxxx 10xxxxxx
 
 
 
 
16
U+0800
U+FFFF
3 1110xxxx 10xxxxxx 10xxxxxx
 
 
 
21
U+10000
U+1FFFFF
4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
 
 
26
U+200000
U+3FFFFFF
5 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
 
31
U+4000000
U+7FFFFFFF
6 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
上の図では、xの位置(すなわち、バイトの最初の0以降のデータ)だけが本物の文字データを格納しているので、次の2つの匿名関数を使用してこれらのデータを抽出します.
rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i > 1 else '') if x else ''
fun = lambda x, i: x[i+1:8] + rec(x[8:], i-1)
fun()は2つのパラメータを受け入れ、最初のパラメータは1つの文字を表すバイナリ文字列であり、このバイナリ文字列は異なる長さ(8、16、24...48)を有する可能性がある.2番目のパラメータは、この文字が何バイトを占めるかです.lambda x, i: x[i+1:8] + rec(x[8:], i-1)x[i+1:8]は、最初のバイトのデータを取得し、rec()を呼び出して、後続のバイトのデータを再帰的に抽出する.
ここで、rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i > 1 else '') if x else ''といえば、式の中でrecを引用したことに理解できないかもしれません.確かに、厳密な意味では再帰は実現できませんが、pythonではこれでいいです.これがpythonの文法糖です.
lambda式を使って再帰を書くのは決して簡単なことではありません.匿名関数の引用自体は簡単ではないので、大牛劉未鵬の博文を参考にすることができます.http://blog.csdn.net/pongba/article/details/1336028
文字のバイトデータでは、最初のバイトの先頭1の数が文字が占めるバイト数であることに注意します.
chartype = binary[index:].index('0') #          ,           0
string.append(chr(int(fun(binary[index:index+length],chartype),2)))行で使用される関数int()およびchrの役割は、次のとおりです.int():2つのパラメータを受け入れます.最初のパラメータは数値文字列で、2番目のパラメータはこの数値文字列が表す数値の進数です.詳細:https://docs.python.org/3/library/functions.html#int chr():パラメータがint値であるパラメータを受け入れ、Unicodeコードポイントがint値である文字を返します.次の図を参照してください.whileサイクルの最後に、現在の文字のインデックスを現在の文字の長さに増やし、次の文字のインデックスを得ます.
これにより、バイナリ文字列のどの部分がどの文字を表しているかを識別し、fun()を呼び出して各文字のデータを取得することができます.
2.4. テスト効果
次のコマンドを入力してテスト用画像を取得します.
$ wget http://labfile.oss.aliyuncs.com/courses/651/coffee.png

コードの後に2行を追加します.
encodeDataInImage(Image.open("coffee.png"), '    ,Hello world!').save('encodeImage.png')
print(decodeImage(Image.open("encodeImage.png")))

ソースファイルがsteganographyだとします.py:
$ python3 steganography

「ハローワールド!」と印刷する必要があります.
三、実験まとめ
本実験は画像隠写術の原理を理解し、これは古典的な技術であり、本試験を完了した後、lsbがいったい何なのか、何の役に立つのかを理解しなければならない.さらに,本実験では多くの「魔法」(シフト,lambda再帰,リスト導出式...)を用いたが,これらの「魔法」はみんなも身につけるべきだ.
四、完全なコード
次のコマンドを使用して、ソースコードをダウンロードします.
$ wget http://labfile.oss.aliyuncs.com/courses/651/steganography.py

すべてのコードを貼り付けます.
from PIL import Image

"""
     PIL             (        0)
"""
def makeImageEven(image):
    pixels = list(image.getdata())  #          : [(r,g,b,t),(r,g,b,t)...]
    evenPixels = [(r>>1<<1,g>>1<<1,b>>1<<1,t>>1<<1) for [r,g,b,t] in pixels]  #         (      )
    evenImage = Image.new(image.mode, image.size)  #              
    evenImage.putdata(evenPixels)  #              
    return evenImage

"""
     bin()    ,             
"""
def constLenBin(int):
    binary = "0"*(8-(len(bin(int))-2))+bin(int).replace('0b','')  #    bin()             '0b',       '0'          8
    return binary

"""
          
"""
def encodeDataInImage(image, data):
    evenImage = makeImageEven(image)  #          0      
    binary = ''.join(map(constLenBin,bytearray(data, 'utf-8'))) #                    
    if len(binary) > len(image.getdata()) * 4:  #            ,     
        raise Exception("Error: Can't encode more than " + len(evenImage.getdata()) * 4 + " bits in this image. ")
    encodedPixels = [(r+int(binary[index*4+0]),g+int(binary[index*4+1]),b+int(binary[index*4+2]),t+int(binary[index*4+3])) if index*4 < len(binary) else (r,g,b,t) for index,(r,g,b,t) in enumerate(list(evenImage.getdata()))] #   binary                 
    encodedImage = Image.new(evenImage.mode, evenImage.size)  #               
    encodedImage.putdata(encodedPixels)  #         
    return encodedImage

"""
          UTF-8    
"""
def binaryToString(binary):
    index = 0
    string = []
    rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i > 1 else '') if x else ''
    # rec = lambda x, i: x and (x[2:8] + (i > 1 and rec(x[8:], i-1) or '')) or ''
    fun = lambda x, i: x[i+1:8] + rec(x[8:], i-1)
    while index + 1 < len(binary):
        chartype = binary[index:].index('0') #          ,           0
        length = chartype*8 if chartype else 8
        string.append(chr(int(fun(binary[index:index+length],chartype),2)))
        index += length
    return ''.join(string)

"""
      
"""
def decodeImage(image):
    pixels = list(image.getdata())  #       
    binary = ''.join([str(int(r>>1<<1!=r))+str(int(g>>1<<1!=g))+str(int(b>>1<<1!=b))+str(int(t>>1<<1!=t)) for (r,g,b,t) in pixels]) #                 
    #           
    locationDoubleNull = binary.find('0000000000000000')
    endIndex = locationDoubleNull+(8-(locationDoubleNull % 8)) if locationDoubleNull%8 != 0 else locationDoubleNull
    data = binaryToString(binary[0:endIndex])
    return data

encodeDataInImage(Image.open("coffee.png"), '    ,Hello world!').save('encodeImage.png')
print(decodeImage(Image.open("encodeImage.png")))