Реализация метода Куттера-Джордана-Боссена в MATLAB
Введение Доброго времени суток, пользователи Хабра!
В этой статье речь пойдет о программной реализации стеганорафического метода Куттера-Джордана-Боссена в MATLAB.
Для тех, кто не знает, поясню: стеганрография — это наука о скрытой передаче информации путём сохранения в тайне самого факта передачи. В данном методе текстовая информация записывается в изображение по определенному алгоритму. С самим алгоритмом вы можете ознакомиться в посте от 11 марта 2011 года «Стеганографический метод Куттера-Джордана-Боссена». Более подробное описание алгоритма можно найти в статье Защелкина, Иващенко, Иванова «Усовершенствование стеганографического скрытия данных Куттера-Джордана-Боссена».
А теперь перейдем непосредственно к коду.
Основной скрипт В связи, с достаточно большим объемом кода, записывать его в одной функции было неудобно, поэтому, код разбит на несколько функций. Скрипт, код которого приведен ниже, позволяет зашифровать текстовое сообщение в изображение, поочередно вызывая каждую функцию.
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)
Три первые строки не являются обязательными для корректной работы скрипта, однако позволяют сделать процесс работы со скриптом более удобным, убирая из памяти компьютера данные, полученные при прошлом вызове скрипта.
close all % закрывает все графические окна программы MATLAB clear; % выполняет очистку рабочей области clc; % выполняет очистку командного окна Считывание изображения в переменную осуществляется с помощью команды imread, а в скобочках указывается название файла и при необходимости — путь к нему.
rgbImg = imread ('BMW-e36.bmp'); Считанное изображение выводится на экран с помощью команды imshow, но для улучшения восприятия результата применяются команды figure (создание нового графического окна) и title (добавление названия в графическом окне).
% вывод на экран исходного изображения figure (1) imshow (rgbImg); title (['Считанный файл *.bmp в режиме RGB']);
Считанное изображение
Текстовое сообщение, которое необходимо зашифровать, необходимо считать из файла и преобразовать его в двоичный код. Эту задачу выполняет функция read_txt. Данная функция возвращает в вызвавший ее скрипт две переменные txt, bin_txt, в которые записан считанный текст в символьном, а также в двоичном виде. Для вывода текста на экран применена команда disp (txt).
% считывание текста из файла [ txt, bin_txt ]=read_txt (); disp (txt); Для записи информации в изображение вызывается функция KDB_write. Для работы данной функции необходимо передать в нее значение следующих переменных: rgbImg, bin_txt. В первой переменной записано исходное изображение, а во вторую — текст, преобразованный в двоичные числа. Функция возвращает значения следующих переменных: rgbImgtxt, s, coords.
В трехмерный массив rgbImgtxt записано изображение со встроенными данными. Вектор s хранит в себе размер двумерного массива bin_txt. Трехмерный массив coords содержит координаты пикселей, которые подверглись изменению, и является своеобразным ключом для последующего извлечения встроенной информации. После выполнения функции на экран выводится изображение, со встроенными данными.
% запись считанной текстовой информации в синий канал изображния [ 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 Главной задачей данной функции является считывание информации из текстового документа и ее преобразование в оптимальный для встраивания вид.
Файл открывается с помощью функции fopen в режиме чтения, на что указывает параметр 'r'.С помощью цикла while, будет выполняться считывание файла до тех пор, пока не будет достигнут его конец (тогда функция feof выдаст значение равное 1 и цикл прекратит выполнение).
В теле цикла выполняется функция fgetl, которая возвращает строку из файла, удаляя символ конца строки. Данная строка записывается в переменную line. После этого переменная line дописывается в конец текстовой переменной 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, в одну строку, записываются только символы, которые в таблице ASKII находятся под номерами от 1040 до 1103 (в этот перечень попадают символы латинского алфавита, кириллицы, арабские цифры, знаки препинания). Для этого необходимо преобразовать переменную типа char в double.
Для преобразования текстовых символов в двоичной код выполняется перевод сначала в тип double, а затем используется функция dec2bin. Для того, чтобы получить окончательный результат в виде числового массива, а не строки, необходимо отнять от dec2bin (double (txt)) символ '0'. Матлаб выполнит эти действия с кодами символов и выдаст результат в числовой форме.
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 Данная функция предназначена для встраивания информации по методу Куттера-Джордана-Боссена в изображение, сохраненное в формате bmp. Из основного скрипта в нее передается оригинальное изображение и двоичный код текста, который будет встраиваться.
Функция определяет размер массива с двоичными кодами символов и размер изображения. На основе полученных результатов генерируются случайным образом два трехмерных массива, содержащие координаты пикселей, в которые будет производиться встраивание. Количество слоев в каждом массиве зависит от значения переменной r, отвечает за то, сколько раз будет встроен один бит. В первом массиве записываются координаты по вертикали, а во втором — по горизонтали. Для удобства эти массивы объединяются в один трехмерный массив.
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); В функции используется три вложенных цикла. Два из них позволяют обращаться к соответствующим битам текстового сообщения, третий — выполняет повторную запись бита.
for i=1: s (1) for j=1: s (2) for k=1: r В переменную Y записывается яркость пикселя, в которой будет проводиться встраивание. Для ее определения необходимо определить значение яркости каждого канала. Изображение формата bmp хранится в виде трехмерной матрицы, состоящей из трех слоев. В каждом слое записано значение яркости для определенного цвета (от 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, coоrds.
Изображение со встроенной информацией
Увидеть внесенные в изображения изменения практически не возможно. В этом можно убедиться, сравнив увеличенные фрагменты изображения до и после встраивания в него информации.
Фрагмент оригинального изображения
Фрагмент изображения со встроенной информацией
Функция извлечения информации из изображения 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 задает количество пикселей, отстоящих от пикселя с измененной яркостью, по которым будет прогнозироваться начальная яркость.
Как и в предыдущей функции, здесь используются три цикла. Они отвечают за обращение к пикселям со встроенной информацией.
sigma=3; r=5; for i=1: s (1) for j=1: s (2) for k=1: r При каждой итерации цикла находится прогнозируемая яркость синего канала пикселя и сравнивается с фактической.
Для того чтобы найти прогнозируемую яркость находится сумма ячеек в строке левее и правее от пикселя с информацией, ячеек расположенных выше и ниже от пикселя. Поскольку при этом значение яркости синего канала самого пикселя со встроенной информацией прибавляется два раза, то его необходимо умножить на 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.
В первом случае, если разница равна 0, а прогнозируемая яркость равна 255, то в переменную del необходимо записать любое положительное число, например, 0,5.
Если же и разница, и прогнозируемая яркости равны 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 раза), то мы получим вектор из трех цифр. Эти цифры могут быть разными, поскольку извлечение информации носит вероятностный характер. Для того что бы найти истинное значение бита зашифрованного текста, необходимо найти среднее арифметическое всех элементов вектора и выполнить округление.
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.«Стеганографический метод Куттера-Джордана-Боссена».2. Статья Защелкина, Иващенко, Иванова «Усовершенствование стеганографического скрытия данных Куттера-Джордана-Боссена».