16 апреля 2011 г.

Гистограммный статистический стегоанализ. Пример практической реализации

Учитывая пожелания читателей и статистику посещаемости для сообщений, этот пост посвящается практической реализации одного из методов стегоанализа (стеганоанализа, steganalysis). Ранее уже приводилось теоретическое обоснование различия гистограмм яркостей цветовых компонент для пустого изображения-контейнера и заполненного стегоизображения.
В русскоязычном интернете невозможно найти хоть какую-нибудь работающую программу для стегоанализа. Однако, теоретические  и практические (коммерческие) разработки в мире шагнули весьма далеко.
Рис. 1 Гистограмма пустого и заполненного изображений 

Не будем отставать и создадим наш собственный скрипт для Matlab, который будет отыскивать последовательное вложение. (Встроить вложение можно, с помощью ранее описанного метода). Метод обнаружения последовательного встраивания будем разрабатывать для 24 битного BMP изображения.
 
По приведенной ранее классификации, описываемый метод стегоанализа относится к
 вероятностным методам, 
 которые в пространственной форме представления изображения 
 с помощью статистического критерия Хи квадрат ищут стеганографические вложения, 
 сделанные определенным алгоритмом.

Кто не понял предыдущее предложение - не переживайте: я сам его до конца не понимаю, а стереть жалко.

Общее описание алгоритма:

1.  Разбиваем изображение на цветовые компоненты.

2.  Каждую цветовую компоненту разбиваем на непересекающиеся прямоугольные блоки достаточной величины (Что такое «достаточная величина» требует особого исследования).

3.  Для каждого блока строим эмпирическую гистограмму яркости. (Эмпирический – основанный на опыте, изучении фактов, опирающийся на непосредственное наблюдение, эксперимент.) На рисунке 1 гистограмма обозначена синим цветом.

4.  Для каждого блока строим теоретическую гистограмму на основе эмпирической. Для построения теоретической гистограммы найдем среднее арифметическое количества пикселей с соседними яркостями. 
Соседними будут яркости 0<->1 2<->3 … 254<->255, или в двоичной системе исчисления:
0000 0000<->0000 0001;
0000 0010<->0000 0011;
... 
1111 1110<->1111 1111.

Теоретическая гистограмма наблюдается при встраивании данных в каждый бит (пиксель) цветовой компоненты. На рисунке 1 - обозначена красным цветом. Можно сказать, что при встраивании количество пикселей в соседних парах распределено равновероятно.

5.  Для каждого блока сравним схожесть теоретической и эмпирической гистограмм с помощью статистики Хи квадрат (Пирсона). Если теоретическая и эмпирическая гистограммы различаются несущественно, то вероятно, что в каждый пиксель цветовой компоненты исследуемого блока встроена скрытая информация. Вычисляем значение статистики для проверяемого блока цветовой компоненты:

  Теоретическая и эмпирическая гистограммы считаются схожими, если  полученное эмпирическое значение статистики Хи квадрат меньше теоретического значения статистики Хи квадрат при заданном уровне значимости а и количестве степеней свободы k-1.
 
 Если данное неравенство справедливо, то мы обнаружили встраивание в проверяемый блок. Однако, хватит уже теории.


Matlab  cкрипт для гистограммного статистического стегоанализа

Данный скрипт является функцией, которую можно вызвать из командной строки MATLAB.

На вход функции подаются: адрес анализируемого изображения, размер непересекающихся прямоугольных блоков по горизонтали и вертикали, уровень значимости а (рекомендуется 0.9).

На выходе получаем изображения трех цветовых компонент: RED_out, GREEN_out, BLUE_out.  
Серым цветом показаны места, где соседние пары яркостей имеют равновероятное распределение, т.е. предполагается встраивание. Красным, зеленым и синим цветом, соответственно, обозначены места цветовых компонент, где встраивания нет. 

function [RED_out,GREEN_out,BLUE_out] = histo_chi2_steganalysis(IMG_in,stepX,stepY,Prbb);
% pirson_Xi_2_Analiz

Читаем картинку из файла, раскладываем ее на цветовые компоненты, вычисляем ее размер.
  Picture = imread(IMG_in,'bmp');
  Red = Picture(:,:,1);
  Green = Picture(:,:,2);
  Blue = Picture(:,:,3);
  [Pheight,Pwidth] = size(Red);
 
Создаем индексные переменные для блоков, на которые будет разбито изображение.

  a_ind_X = 1:stepX:Pwidth;
  a_ind_Y = 1:stepY:Pheight;
   
Подсчитываем, сколько блоков по горизонтали и по вертикали у нас будет.

  countX = size(a_ind_X,2);
  countY = size(a_ind_Y,2);
Добавляем к изображению широкую «границу» из пикселей, чтобы крайние блоки не были обрезаны границей изображения.

  brdrd_Red = [Red;Red((countY-1)*stepY:-1:(countY-2)*stepY+1,:)];
  brdrd_Red = [brdrd_Red,brdrd_Red(:,(countX-1)*stepX:-1:(countX-2)*stepX+1)];

  brdrd_Green = [Green; Green((countY-1)*stepY:-1:(countY-2)*stepY+1,:)];
  brdrd_Green = [brdrd_Green, brdrd_Green(:,(countX-1)*stepX:-1:(countX-2)*stepX+1)];

  brdrd_Blue = [Blue;Blue((countY-1)*stepY:-1:(countY-2)*stepY+1,:)];
  brdrd_Blue = [brdrd_Blue,brdrd_Blue(:,(countX-1)*stepX:-1:(countX-2)*stepX+1)];

Формируем изображение с добавленными границами.

  brdrd_Picture(:,:,1) = brdrd_Red(:,:);
  brdrd_Picture(:,:,2) = brdrd_Green(:,:);
  brdrd_Picture(:,:,3) = brdrd_Blue(:,:);

Зададим размеры массивов для хранения гистограммы, наблюдаемого значения Хи квадрат - ArrayReal и теоретического значения Хи квадрат - ArrayTeor:

  HistoTeor=zeros(1,256);
  ArrayReal = zeros(size(a_ind_Y,2),size(a_ind_X,2));
  ArrayTeor = zeros(size(a_ind_Y,2),size(a_ind_X,2));

В цикле будем обращаться последовательно к первой, второй и третьей цветовой компонентам.

  for Z = 1:3
  IMG = brdrd_Picture(:,:,Z);  

Для каждой цветовой компоненты последовательно переберем все, исследуемые на предмет встраивания, блоки. Для каждого блока определим эмпирическое и теоретическое значения Хи квадрат (процесс достаточно подробно прокомментирован в самом коде).

    for Y = 1:stepY:Pheight
      for X = 1:stepX:Pwidth

        areaXY = IMG(Y:Y+stepY,X:X+stepX);

        % POSTROENIE EMPIRICHESKOI GISTOGRAMMY
        % REZUL'TAT - VERTIKAL'NYI VEKTOR #HistoEmpir#
        HistoEmpir = imhist(areaXY);

        % TRANSPONIROVANIE VEKTORA #HistoEmpir#
        % REZUL'TAT - GORIZONTAL'NYI VEKTOR #HistoEmpir#
        HistoEmpir = transpose(HistoEmpir);

        % POSTROENIE TEORETIChESKOI GISTOGRAMMY #HistoTeor# 
        % REZUL'TAT - GORIZONTAL'NYI VEKTOR #HistoTeor#
        k = 1:2:256;
        HistoTeor(k) = (HistoEmpir(k)+HistoEmpir(k+1))/2;
        HistoTeor(k+1) = (HistoEmpir(k)+HistoEmpir(k+1))/2;
%*******************************************************************
        % RASChET VEROYaTNOSTEI EMPIRICHESKOI i TEORETIChESKOI YaRKOSTEI #VarTeor# #VarEmpir#
        % REZUL'TAT - GORIZONTAL'NYE VEKTORA #VarEmpir# #VarTeor#

        VarEmpir = HistoEmpir;
        VarTeor = HistoTeor;

        zero_ind = find(VarTeor <=20);

        VarEmpir(zero_ind) = [];
        VarTeor(zero_ind) = [];

        % RASChET MASSIVA-SYR'Ya DLYa RASChETA REAL'NOGO ZNAChENIYa #Xi2#
            % REZUL'TAT - GORIZONTAL'NYI VEKTOR #Xi2#
        Xi2 = ((VarEmpir-VarTeor).^2)./VarTeor;

        % UDALENIE OShIBOChNYH ZNAChENII DELENIYa NA NOL' IZ #Xi2#
        Xi2 = Xi2(~isnan(Xi2));

        % RASChET REAL'NOGO ZNAChENIYa #Xi2#
        Xi2Real = sum(Xi2);

        % RASChET TABLIChNOGO ZNAChENIYa #Xi2#
        Xi2Teor = chi2inv(Prbb,size(Xi2,2)-1);
        % FORMIROVANIE NAGLYaDNYH MASSIVOV REAL'NOGO I TABLIChNOGO ZNAChENII #Xi2#
        ArrayReal((Y-1)/stepY+1,(X-1)/stepX+1) = Xi2Real;
        ArrayTeor((Y-1)/stepY+1,(X-1)/stepX+1) = Xi2Teor;
      end;
    end;

Результаты расчетов статистик Хи квадрат занесем в соответствующие массивы для первой, второй и третьей цветовых компонент. Массивы ArrayCompare хранят логические значения для каждой из анализируемых областей: «1» - встраивание есть, «0» - встраивания нет.

    if (Z == 1)
      RED_ArrayReal = ArrayReal;
      RED_ArrayTeor = ArrayTeor;
      RED_ArrayCompare = uint8(RED_ArrayReal
    end;
    if (Z == 2)
      GREEN_ArrayReal = ArrayReal;
      GREEN_ArrayTeor = ArrayTeor;
      GREEN_ArrayCompare = uint8(GREEN_ArrayReal
    end;
    if (Z == 3)
      BLUE_ArrayReal = ArrayReal./ArrayTeor;
      BLUE_ArrayTeor = ArrayTeor./ArrayTeor;
      BLUE_ArrayCompare = uint8(BLUE_ArrayReal
    end;
  end;

На данном этапе у нас уже есть информация о «загрязненности» блоков изображения встраиванием. Для удобства пользователя покажем данные блоки на изображении.
В цикле перебираются все анализируемые блоки для каждой компоненты. Если анализируемый блок чистый, выводим его «родным» (красным, зеленым или синим) для компоненты цветом. Если блок со встраиванием, выводим его серым цветом

  for Y = 1:stepY:Pheight
    for X = 1:stepX:Pwidth
      RED_out(Y:Y+stepY-1,X:X+stepX-1,1) = brdrd_Red(Y:Y+stepY-1,X:X+stepX-1);
      RED_out(Y:Y+stepY-1,X:X+stepX-1,2) = brdrd_Red(Y:Y+stepY-1,X:X+stepX-1).*RED_ArrayCompare((Y-1)/stepY+1,(X-1)/stepX+1);
      RED_out(Y:Y+stepY-1,X:X+stepX-1,3) = brdrd_Red(Y:Y+stepY-1,X:X+stepX-1).*RED_ArrayCompare((Y-1)/stepY+1,(X-1)/stepX+1);
     
      GREEN_out(Y:Y+stepY-1,X:X+stepX-1,1) = brdrd_Green(Y:Y+stepY-1,X:X+stepX-1).*GREEN_ArrayCompare((Y-1)/stepY+1,(X-1)/stepX+1);
      GREEN_out(Y:Y+stepY-1,X:X+stepX-1,2) = brdrd_Green(Y:Y+stepY-1,X:X+stepX-1);
      GREEN_out(Y:Y+stepY-1,X:X+stepX-1,3) = brdrd_Green(Y:Y+stepY-1,X:X+stepX-1).*GREEN_ArrayCompare((Y-1)/stepY+1,(X-1)/stepX+1);

      BLUE_out(Y:Y+stepY-1,X:X+stepX-1,1) = brdrd_Blue(Y:Y+stepY-1,X:X+stepX-1).*BLUE_ArrayCompare((Y-1)/stepY+1,(X-1)/stepX+1);
      BLUE_out(Y:Y+stepY-1,X:X+stepX-1,2) = brdrd_Blue(Y:Y+stepY-1,X:X+stepX-1).*BLUE_ArrayCompare((Y-1)/stepY+1,(X-1)/stepX+1);
      BLUE_out(Y:Y+stepY-1,X:X+stepX-1,3) = brdrd_Blue(Y:Y+stepY-1,X:X+stepX-1);
   
    end;
  end;

Избавляемся от широкой границы, добавленной для работы с крайними блоками.

  RED_out = RED_out(1:Pheight,1:Pwidth,:);
  GREEN_out = GREEN_out(1:Pheight,1:Pwidth,:);
  BLUE_out = BLUE_out(1:Pheight,1:Pwidth,:);

На данном этапе функция уже сформировала переменные-изображения, на которых видны результаты анализа. Для удобства добавим вывод результатов на экран. По желанию следующие три строки можно закомментировать или удалить.

  figure;imagesc(RED_out);
  figure;imagesc(GREEN_out);
  figure;imagesc(BLUE_out);
end


Практическое использование функции для стегоанализа последовательного вложения

Кратко посмотрим, как работает наша функция.
Для анализа возьмем изображение 'stego_murzik3.bmp', ширину и высоту блока возьмем в четверть размера изображения, уровень значимости зададим 0.8:

[RED_out,GREEN_out,BLUE_out] = histo_chi2_steganalysis('stego_murzik3.bmp',512,512,0.8);

В результате получаем следующие изображения:

Красная компонента вся отображается серым цветом, что означает полное встраивание в красную компоненту. Зеленая компонента – зелена лишь наполовину, что проявляет области со встраиванием (серая) и без встраивания (зеленая). Синяя компонента показывает отсутствие встраивания по всей площади.
Кажется все замечательно. Однако попробуем уменьшить размер блоков, на которые изображение разбивается для анализа. Используем блоки размером 200х200 пикселей:

[RED_out,GREEN_out,BLUE_out] = histo_chi2_steganalysis('stego_murzik3.bmp',200,200,0.8);

Как видим, в местах определенных предыдущим анализом как чистые от встраивания оно "появилось". Это говорит о важности выбора размера и формы блоков, на которые разбивается для анализа изображение.

Результат прохода узкими вертикальными полосками 30х1023:
[RED_out,GREEN_out,BLUE_out] = histo_chi2_steganalysis('stego_murzik3.bmp',30,1023,0.8);


Анализ квадратиками 30х30:
[RED_out,GREEN_out,BLUE_out] = histo_chi2_steganalysis('stego_murzik3.bmp',30,30,0.8);



Все желающие могут скачать данную функцию и изображения, на которых можно самостоятельно посмотреть, как она работает.

17 комментариев:

  1. Полезная статья, спасибо!

    ОтветитьУдалить
  2. Пожалуйста, рад помочь.
    Если еще будут какие-то вопросы - спрашивайте.

    ОтветитьУдалить
  3. Спасибо! очень интересная статья!
    но у меня возник вопрос о том как бы извлечь тот самый том "войны и мир", так сказать демодулировать изображение. возможно ли это вообще? был бы очень признателен.

    ОтветитьУдалить
  4. В более ранних постах можно посмотреть описание и скрипты для сокрытия данных в НЗБ:

    http://gr1g0ry.blogspot.com/2011/03/steganography-in-matlab.html

    и для извлечения скрытых данных:

    http://gr1g0ry.blogspot.com/2011/03/steganography-in-matlab-extracting.html

    ОтветитьУдалить
  5. Здравствуйте!
    Решил протестировать Ваш скрипт. При запуске выдает:
    ??? Input argument "IMG_in" is undefined.

    Error in ==> histo_chi2_steganalysis at 5
    Picture = imread(IMG_in,'bmp');

    Файл IMG_in.bmp находится в одной папке с файлом скрипта. Подскажите в чем проблема?

    Спасибо!

    ОтветитьУдалить
    Ответы
    1. Добрий день! потрібна ваша допомого, я попробував скачати скріпт проте ссилка на даний час застаріла! Якщо у вас залишились матеріали по даній темі буду вдячний якщо скинете на пошту: Black7moke@gmail.com
      Дякую

      Удалить
  6. Данный пример, в отличие от предыдущих, написан в виде функции для Matlab. Поэтому его нужно запускать из командной строки.

    Например:

    >>[RED_out,GREEN_out,BLUE_out] = histo_chi2_steganalysis('stego_murzik3.bmp',512,512,0.8);

    Надеюсь ответ актуален.

    ОтветитьУдалить
  7. Заработало.=) Спасибо за ответ!

    ОтветитьУдалить
  8. Здравстуйте!

    Подскажите, пожалуйста, что нужно указывать при использовании материалов с Вашего сайта?

    Спасибо.

    ОтветитьУдалить
  9. Если Вы пишете курсовую или диплом, наверное лучше будет сослаться на мои статьи в печатных изданиях, например:

    http://www.nbuv.gov.ua/portal/Soc_Gum/VSUNU/2007_5_1/15.pdf

    или

    http://www.google.com.ua/search?hl=ru&client=opera&rls=ru&channel=suggest&biw=1020&bih=635&q=%D0%A1%D1%82%D0%B0%D1%82%D0%B8%D1%81%D1%82%D0%B8%D1%87%D0%BD%D0%B8%D0%B9+%D0%BC%D0%B5%D1%82%D0%BE%D0%B4+%D1%81%D1%82%D0%B5%D0%B3%D0%B0%D0%BD%D0%BE%D0%B0%D0%BD%D0%B0%D0%BB%D1%96%D0%B7%D1%83&aq=f&aqi=&aql=&oq=

    Если собираетесь публиковать что либо в интернете, пожалуйста поставьте ссылку на этот сайт.

    ОтветитьУдалить
  10. Благодарю, Григорий. Занимательная тема.
    После прочтения книги "Цифровая Стеганография" тоже увлекся (правда писать приходится на VBA ;)).
    Представленные ранее методы, они хоть и простые, но абсолютно не устойчивые к обработке. Будут ли вашем блоге рассматриваться более робастные (устойчивые) алгоритмы?

    ОтветитьУдалить
  11. Спасибо за вопрос.
    Да я действительно хочу сделать посты о внедрении скрытых данных в JPEG и JPEG2000. Сейчас есть заделы на пост про встраивание в НЗБ рассеяно по площади всего изображения.(Что немного более устойчиво к стегоанализу, но все так же неустойчиво к удалению сокрытой информации.)

    ОтветитьУдалить
  12. перезалейте скрипт !!!

    ОтветитьУдалить
  13. Да если можно перезалейте скрипт

    ОтветитьУдалить
  14. Спасибо большое за ваши статьи. Очень интересно вас читать. Совмещаю приятное с полезным, так сказать. Если вам не трудно, пришлите, пожалуйста, скрипт на lisfo4ka@gmail.com. Буду очень, очень признательна :)

    ОтветитьУдалить
  15. Большая просьба подправить статью.
    Выражения для определения массива BLUE_ArrayCompare "обрезаны" на самом интересном: RED_ArrayCompare = uint8(RED_ArrayReal
    Для других цветов так же.

    ОтветитьУдалить
  16. Здравствуйте! У меня такой вопрос где можно скачать данную программу! Спасибо

    ОтветитьУдалить