MATLABでのKutter-Jordan-Bossenメソッドの実装

はじめに


こんにちは、Habrのユーザー!



この記事では、MATLABでのKutter-Jordan-Bossenステガノグラフィ法のソフトウェア実装に焦点を当てます。



知らない人のために、私は説明します。ステガノグラフィーは、伝達の事実そのものを秘密にしておくことによる情報の隠された伝達の科学です。 この方法では、特定のアルゴリズムに従ってテキスト情報が画像に記録されます。 2011年3月11日付の投稿「Kutter-Jordan-Bossenのステガノグラフィック手法」で、アルゴリズムに慣れることができます アルゴリズムのより詳細な説明は、イヴァノフ、イヴァシュチェンコのザシュチェルキンによる記事「Kutter-Jordan-Bossenデータのステガノグラフ隠蔽の改善」にあります。



それでは、コードに直接行きましょう。



メインスクリプト


かなり大量のコードがあるため、1つの関数で記述するのは不便でした。したがって、コードはいくつかの関数に分割されます。 以下にコードを示したスクリプトを使用すると、画像内のテキストメッセージを暗号化して、各関数を順番に呼び出すことができます。



close all %      MATLAB clear; %     clc; %     %   RGB- rgbImg = imread('BMW-e36.bmp'); %      figure(1) imshow(rgbImg); title(['  *.bmp   RGB']); %     [ txt, bin_txt ]=read_txt(); disp(txt); %         [ rgbImgtxt, s, coords ]=KDB_write( rgbImg, bin_txt ); figure(2) imshow( rgbImgtxt ); title([ '   ' ]); %       [txt_new]=KDB_pull_out(rgbImgtxt,s,coords); disp(txt_new)
      
      







最初の3行は、スクリプトが正しく機能するために必要ではありませんが、コンピューターのメモリからスクリプトへの最後の呼び出しからデータを削除することにより、スクリプトを使用するプロセスをより便利にすることができます。



 close all %      MATLAB clear; %     clc; %    
      
      







イメージはimreadコマンドを使用して変数に読み込まれ、ファイルの名前は括弧で示され、必要に応じてパスが示されます。



 rgbImg = imread('BMW-e36.bmp');
      
      







読み込まれたイメージはimshowコマンドを使用して表示されますが、Figure(新しいグラフィックウィンドウを作成する)コマンドとタイトル(グラフィックウィンドウに名前を追加する)コマンドを使用して、結果の認識を改善します。



 %      figure(1) imshow(rgbImg); title(['  *.bmp   RGB']);
      
      











画像を読み取ります。



暗号化するテキストメッセージは、ファイルから読み取り、バイナリに変換する必要があります。 このタスクは、read_txt関数によって実行されます。 この関数は、2つの変数txt、bin_txtを呼び出したスクリプトに返します。このテキストには、読み取られたテキストが記号形式とバイナリ形式で書き込まれます。 画面にテキストを表示するには、disp(txt)コマンドを使用します。



 %     [ txt, bin_txt ]=read_txt(); disp(txt);
      
      







イメージに情報を書き込むには、KDB_write関数が呼び出されます。 この関数が機能するには、次の変数の値を渡す必要があります:rgbImg、bin_txt。 最初の変数には元の画像が含まれ、2番目の変数には2進数に変換されたテキストが含まれます。 この関数は、変数rgbImgtxt、s、coordsの値を返します。



データが埋め込まれた画像は、rgbImgtxt 3次元配列に書き込まれます。 ベクトルsは、2次元のbin_txt配列のサイズを格納します。 3次元配列座標には、変更されたピクセルの座標が含まれており、埋め込まれた情報を後で抽出するためのキーの一種です。 関数が実行されると、データが埋め込まれた画像が画面に表示されます。



 %         [ rgbImgtxt, s, coords ]=KDB_write( rgbImg, bin_txt ); figure(2) imshow( rgbImgtxt ); title([ '   ' ]);
      
      







KDB_pull_out関数は、画像から埋め込み情報を取得し、画面に表示される暗号化されたテキストをユーザーに返します。



 [txt_new]=KDB_pull_out(rgbImgtxt,s,coords); disp(txt_new)
      
      







次に、各機能の動作を個別に検討します。



テキストファイルから情報を読み取る機能


元の画像を読み取った後、テキストメッセージを読む必要があります。 これには、read_txt()関数が使用されます。



 function [ txt, bin_txt ] = read_txt ( ) %         %            %  txt,      txt_r=''; txt=''; text1=fopen('message.txt','r'); while feof(text1)==0 line=fgetl(text1); txt_r=char(txt_r,line); end fclose(text1); txt(1,:)=txt_r(find((double(txt_r)>1040) + (double(txt_r)<1103))); s=size(txt); txt=txt(2:2:s(2)); bin_txt=dec2bin(double(txt))-'0'; end
      
      







この関数の主な目的は、テキストドキュメントから情報を読み取り、埋め込みに最適なビューに変換することです。



'r'パラメーターで示されるように、ファイルは読み取りモードでfopen関数を使用して開かれます。

whileループを使用して、ファイルはその終わりに達するまで読み取られます(その後、feof関数は値1を返し、ループは実行を停止します)。



ループの本体では、fgetl関数が実行され、ファイルから行が返され、行末記号が削除されます。 この行は、行変数に書き込まれます。 その後、変数行がテキスト変数txt_rの最後に追加されます。



情報を受け取ったら、テキストファイルを閉じる必要があります。



 txt_r=''; txt=''; text1=fopen('message.txt','r'); while feof(text1)==0 line=fgetl(text1); txt_r=char(txt_r,line); end fclose(text1);
      
      







その後、関数findを使用して、txt変数に1行で、1040から1103の数字を持つASKIIテーブルにある文字のみが書き込まれます(ラテンアルファベット、キリル文字、アラビア数字、句読点がこのリストに含まれます)。 これを行うには、char型の変数をdoubleに変換する必要があります。



テキスト文字をバイナリに変換するには、最初に変換をdoubleに変換してから、dec2bin関数を使用します。 文字列ではなく数値配列の形式で最終結果を取得するには、dec2binから文字「0」を減算する必要があります(double(txt))。 Matlabはこれらのアクションを文字コードで実行し、結果を数値形式で表示します。



 txt(1,:)=txt_r(find((double(txt_r)>1040) + (double(txt_r)<1103))); s=size(txt); txt=txt(2:2:s(2)); bin_txt=dec2bin(double(txt))-'0';
      
      







画像に情報を記録する機能


 function [ rgbImgtxt, s, coords ] = KDB_write( rgbImg, bin_txt ) %       -- %     1    1  , %         ,     L=0.1; % ,        r=5; %      s=size(bin_txt); simg=size(rgbImg); coordy=randi([4,simg(1)-3],s(1),s(2),r); coordx=randi([4,simg(2)-3],s(1),s(2),r); coords=cat(3, coordy, coordx); rgbImgtxt=rgbImg; for i=1 : s(1) for j=1 : s(2) for k=1 : r %   Y=(0.298*rgbImg(coords(i,j,k),coords(i,j,k+3),1))+(0.586*rgbImg(coords(i,j,k),coords(i,j,k+3),2))+(0.114*rgbImg(coords(i,j,k),coords(i,j,k+3),3)); if (Y==0) Y=5/L; end %        %    if (bin_txt(i,j)==1) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=double(rgbImg(coords(i,j,k), coords(i,j,k+3), 3)+L*Y); else rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=double(rgbImg(coords(i,j,k), coords(i,j,k+3), 3)-L*Y); end if (rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)>255) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=255; end if (rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)<0) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=0; end end end end end
      
      







この関数は、butter形式で保存された画像にKutter-Jordan-Bossenメソッドを使用して情報を埋め込むように設計されています。 メインスクリプトから、元の画像と埋め込まれるテキストのバイナリコードがそこに転送されます。



この関数は、バイナリ文字コードと画像のサイズを使用して配列のサイズを決定します。 得られた結果に基づいて、埋め込みが実行されるピクセルの座標を含む2つの3次元配列がランダムに生成されます。 各配列のレイヤー数は変数rの値に依存し、1ビットが何回埋め込まれるかを決定します。 最初の配列は垂直座標を記録し、2番目の配列は水平座標を記録します。 便宜上、これらの配列は1つの3次元配列に結合されます。



 s=size(bin_txt); simg=size(rgbImg); coordy=randi([4,simg(1)-3],s(1),s(2),r); coordx=randi([4,simg(2)-3],s(1),s(2),r); coords=cat(3, coordy, coordx);
      
      







この関数は、3つのネストされたループを使用します。 それらのうちの2つは、テキストメッセージの対応するビットにアクセスできるようにし、3つ目はビットを書き換えます。



 for i=1 : s(1) for j=1 : s(2) for k=1 : r
      
      







埋め込みが実行されるピクセルの輝度は、変数Yに書き込まれます。 それを決定するには、各チャンネルの輝度値を決定する必要があります。 bmpイメージは、3つのレイヤーで構成される3次元マトリックスとして保存されます。 各レイヤーには、特定の色の輝度値が記録されます(0〜255)。 結果の輝度値に特定の定数を乗算し、他のチャンネルの輝度値と合計します。 輝度値が0であることが判明した場合、5 / Lに等しい値を変数に割り当てる必要があります。



 Y=(0.298*rgbImg(coords(i,j,k),coords(i,j,k+3),1))+(0.586*rgbImg(coords(i,j,k),coords(i,j,k+3),2))+(0.114*rgbImg(coords(i,j,k),coords(i,j,k+3),3)); if (Y==0) Y=5/L; end
      
      







情報の直接埋め込みは、条件ステートメントを使用して行われます。 テキストメッセージのビットが1の場合、画像の青チャンネルの輝度は、積L * Yに等しい量だけ増加します。 それ以外の場合、この値は画像の青チャンネルの輝度から差し引かれます。 オーバーフローの可能性を回避するには、データ型uint8をdoubleに変更する必要があります。



 if (bin_txt(i,j)==1) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=double(rgbImg(coords(i,j,k),coords(i,j,k+3),3)+L*Y); else rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=double(rgbImg(coords(i,j,k),coords(i,j,k+3),3)-L*Y); end
      
      







青チャネルの輝度を変更した後、輝度値が255を超える場合、255に設定する必要があります。輝度値が0未満の場合は、0に設定します。



  if (rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)>255) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=255; end if (rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)<0) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=0; end
      
      







サイクルのすべての反復が完了すると、メッセージに画像が完全に埋め込まれます。 特に少量の埋め込みテキストでは、埋め込みが行われたことを視覚的に判断することは事実上不可能です。



この関数は、呼び出し元のスクリプトにrgbImgtxt、s、coordsという3つの変数を返します。







情報が埋め込まれた画像



画像に加えられた変更を確認することはほとんど不可能です。 これは、情報を埋め込む前後の画像の拡大断片を比較することで確認できます。







元の画像の断片







情報が埋め込まれた画像フラグメント



画像から情報を抽出する機能


 function [ txt_new ]=KDB_pull_out( rgbImgtxt, s, coords ) %       -- %         %   .      %          . rgbImgprog=rgbImgtxt; sigma=3; r=5; for i=1 : s(1) for j=1 : s(2) for k=1 : r %    rgbImgprog=(double(sum(rgbImgtxt(coords(i,j,k)-sigma:coords(i,j,k)+sigma,coords(i,j,k+3),3)))+double(sum(rgbImgtxt(coords(i,j,k),coords(i,j,k+3)-sigma:coords(i,j,k+3)+sigma,3)))-2*double(rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)))/(4*sigma); %        %    del=double(rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3))-rgbImgprog; if ( and(del==0,rgbImgprog==255) ) del=0.5; end if ( and(del==0,rgbImgprog==0) ) del=-0.5; end if ( del>0 ) kat(k)=1; else kat(k)=0; end end txt_new_bin(i,j)=round(sum(kat)/r); end end txt_new_doub=bin2dec(num2str(txt_new_bin)); s=size(txt_new_doub); txt_new=char(reshape(txt_new_doub,s(2),s(1))); end
      
      







関数が機能するためには、情報が埋め込まれた画像、文字のバイナリコードが記録された配列のサイズ、および埋め込みが実行されたピクセルの座標を持つ配列を転送する必要があります。



sigma変数は、輝度が変更されたピクセルから間隔を空けたピクセル数を設定します。このピクセルから、初期輝度が予測されます。



前の関数と同様に、ここでは3つのサイクルが使用されます。 情報が埋め込まれたピクセルへのアクセスを担当します。



  sigma=3; r=5; for i=1 : s(1) for j=1 : s(2) for k=1 : r
      
      







サイクルの各反復で、ピクセルの青チャネルの予測輝度が検出され、実際の輝度と比較されます。



予測された明るさを見つけるために、情報のあるピクセルの左と右の行のセルの合計があります。セルはピクセルの上下にあります。 情報が埋め込まれたピクセル自体の青チャンネルの輝度値は2回加算されるため、2を乗算して減算し、輝度の計算に使用されるピクセル数で得られる数で割る必要があります。この関数では12です。



 rgbImgprog=(double(sum(rgbImgtxt(coords(i,j,k)-sigma:coords(i,j,k)+sigma,coords(i,j,k+3),3)))+double(sum(rgbImgtxt(coords(i,j,k),coords(i,j,k+3)-sigma:coords(i,j,k+3)+sigma,3)))-2*double(rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)))/(4*sigma);
      
      







メッセージを解読するには、青チャネルの実際の輝度と予測された輝度の差を見つける必要があります。 実際の輝度と予測された輝度の差が0の場合、2つのオプションも考慮する必要があります。



最初のケースでは、差が0で、予測輝度が255の場合、0.5などの正数をdel変数に書き込む必要があります。



差と予測輝度の両方が0の場合、負の数値が変数delに書き込まれます。



 del=double(rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3))-rgbImgprog; if ( and(del==0,rgbImgprog==255) ) del=0.5; end if ( and(del==0,rgbImgprog==0) ) del=-0.5; end
      
      







メッセージを抽出するには、変数delの値を0と比較する必要があります。0より大きい場合、ベクトルkatに1が書き込まれます。それ以外の場合は0です。



 if ( del>0 ) kat(k)=1; else kat(k)=0; end
      
      







各ビットは特定の回数(この場合は3回)イメージに書き込まれるため、3桁のベクトルを取得します。 情報の抽出は本質的に確率的であるため、これらの数値は異なる場合があります。 暗号文のビットの真の値を見つけるには、ベクトルのすべての要素の算術平均を見つけて丸めを実行する必要があります。



 txt_new_bin(i,j)=round(sum(kat)/r);
      
      







メッセージのすべてのビットの値が見つかったら、それらをchar型に変換し、文字列として書き込む必要があります。



 txt_new_doub=bin2dec(num2str(txt_new_bin)); s=size(txt_new_doub); txt_new=char(reshape(txt_new_doub,s(2),s(1)));
      
      







その後、関数は抽出されたメッセージとともにtxt_new変数をスクリプトに渡します。 このメッセージはソーステキストとわずかに異なる場合がありますが、読みやすいままです。 エラーの発生は、明るさの予測が常に客観的な結果をもたらすとは限らないという事実によるものです(たとえば、画像上に多数の小さな対照的な細部が存在するなど)。



「バイエルンモータープラント」というフレーズを画像に埋め込みました。 情報の埋め込みと抽出を10回試行した結果、次の結果が得られました。



     >     0                    
      
      







この不正確さを避けるために、1ビットの埋め込み数を増やし、予測される明るさに応じて「十字」のサイズを大きくすることができます。 ただし、これらの方法はある程度有効です。 最良の結果を得るには、ノイズのないコーディングを使用する必要があります。



ご清聴ありがとうございました!



使用されるソースのリスト:



1. 「Kutter-Jordan-Bossenのステガノグラフィ法」。

2. イヴァシュチェンコ、イヴァノフ、ザシュチェルキンによる記事「Kutter-Jordan-Bossenデータのステガノグラフィによる隠蔽の改善」



All Articles