Время прочтения: 4 мин.

Интернет дает практически неограниченный доступ к огромному количеству информации всех видов по всем миру. Почти любую информацию легко скопировать, видоизменить и распространить. Поэтому вопросы защиты информации от несанкционированного копирования, изменения и распространения стали очень актуальны. Одно из средств защиты — цифровые водяные знаки (ЦВЗ). ЦВЗ — эффективный метод встраивания специальной метки в цифровой контент. Они могут быть использованы в различных приложениях, включая защиту авторских прав, аутентификацию, защиту от изменения и т. д.

          В статье рассмотрим ЦВЗ в изображениях. Будем использовать систему встраивания информации (СВИ) в наименее значимые биты изображения (НБЗ).

Метод НБЗ основан на недостатках человеческого зрения: глаз человека плохо улавливает незначительные изменения цветов. А именно изменение младших битов даёт такое незначительное изменение цвета. Поэтому мы можем встраивать в них информацию, не боясь ее раскрытия. Метод НБЗ также очень эффективен в стеганографии.

          Напишем мини-приложение НБЗ-встраивания черно-белого водяного знака в цветное изображение в среде C++ Builder с использованием языка C++. Оба изображения для простоты берём одинакового размера. Чтобы мы могли увидеть ЦВЗ, встроим его в первые два бита. Чтобы ЦВЗ сделать более невидимой, нужно производить встраивание в 0 бит.

          Так как глаз человека наиболее чувствителен к яркости изображения и несколько менее к цветности, для наглядности будем выполнять встраивание в компоненту яркости. Для этого нам необходимо перейти от цветового пространства RGB (Red, Green, Blue) к YUV(Y — яркость, U и V — цветоразностные компоненты). Переход будем выполнять по общеизвестным формулам (смотри в коде под комментарием «преобразование RGB в YUV»). Чтобы ЦВЗ была более незаметна человеку, нужно производить встраивание в одну из цветоразностных компонент.

На главной форме создаём 7 объектов: Image1 — исходное изображение,  Image2 — черно-белое изображение водяного знака, Button1 – «Открыть 1», кнопка открытия исходного изображения из файла, Button2 — «Открыть 2», кнопка открытия изображения водяного знака, OpenPictureDialog1 — отвечает за открытие диалогового окна при нажатии на Button1, OpenPictureDialog2 — то же самое, что и OpenPictureDialog1, только для Button2, Button3 — «Встроить», кнопка осуществления встраивания.

Код, который выполнится при нажатии на Button1. Для Button2 меняются индексы у OpenPictureDialog и Image.

int W, H;
OpenPictureDialog1->DefaultExt = "BMP";
 OpenPictureDialog1->FileName = "*.bmp";
 OpenPictureDialog1->InitialDir = "";
if (OpenPictureDialog1->Execute())
 Image1->Picture->LoadFromFile(OpenPictureDialog1->FileName);
 Image1->Stretch=true;
 Bitmap->Assign(Image1->Picture);
//ширина изображения
 W=Image1->Width;
//высота изображения
 H=Image1->Height;

Код, который выполняется при нажатии на кнопку Button3.

//объявление массивов
int R1,G1,B1,R2,G2,B2,R3,G3,B3;
int **U1; U1=new int*[H]; for(int j=0; j<H; j++) U1[j]=new int[W];
int **V1; V1=new int*[H]; for(int j=0; j<H; j++) V1[j]=new int[W];
int **Y1; Y1=new int*[H]; for(int j=0; j<H; j++) Y1[j]=new int[W];
int **U2; U2=new int*[H]; for(int j=0; j<H; j++) U2[j]=new int[W];
int **V2; V2=new int*[H]; for(int j=0; j<H; j++) V2[j]=new int[W];
int **Y2; Y2=new int*[H]; for(int j=0; j<H; j++) Y2[j]=new int[W];
int **U3; U3=new int*[H]; for(int j=0; j<H; j++) U3[j]=new int[W];
int **V3; V3=new int*[H]; for(int j=0; j<H; j++) V3[j]=new int[W];
int **Y3; Y3=new int*[H]; for(int j=0; j<H; j++) Y3[j]=new int[W];
 BYTE* biImage1; BYTE* biImage2;
// сканирование первых пиксельных строк
for(int i=0; i<H; i++)
{
biImage1=(BYTE*)(Form1->Image1)->Picture->Bitmap->ScanLine[i];
biImage2=(BYTE*)(Form1->Image2)->Picture->Bitmap->ScanLine[i];
 // сканирование каждого пикселя в строке
 for(int j=0; j<W; j++)
 {
R1=biImage1[j*4+2]; G1=biImage1[j*4+1]; B1=biImage1[j*4];
R2=biImage2[j*4+2]; G2=biImage2[j*4+1]; B2=biImage2[j*4];
// преобразование RGB в YUV
Y1[i][j] = 0.299 * R1 + 0.587 * G1 + 0.114 * B1;
Y2[i][j] = 0.299 * R2 + 0.587 * G2 + 0.114 * B2;
U1[i][j] = -0.14713 * R1 - 0.28886 * G1 + 0.436 * B1 + 128;
V1[i][j] = 0.615 * R1 - 0.51499 * G1 - 0.10001 * B1 + 128;
 //встраивание ЦВЗ
if ((R2==0)and(G2==0)and(B2==0)) {
 int b1=(Y1[i][j]%4-Y1[i][j]%2)%2;
int b2=(Y1[i][j]%8-Y1[i][j]%4)%2;
 if (b1==1){Y1[i][j]=Y1[i][j]-2;};
     if (b2==1){Y1[i][j]=Y1[i][j]-4;}
}
 else {
 int b1=(Y1[i][j]%4-Y1[i][j]%2)%2;
int b2=(Y1[i][j]%8-Y1[i][j]%4)%2;
 if (b1==0){Y1[i][j]=Y1[i][j]+2;};     
if (b2==0){Y1[i][j]=Y1[i][j]+4;}
}
  Y3[i][j]=Y1[i][j];
 U3[i][j]=U1[i][j];
 V3[i][j]=V1[i][j];
  }
 }
//преобразование YUV в RGB
for(int i=0; i<H; i++)
 {
biImage1=(BYTE*)(Form1->Image1)->Picture->Bitmap->ScanLine[i];
for(int j=0; j<W; j++)
 {
//красный R
R3 = Y3[i][j] + 1.13983 * (V3[i][j] - 128);
if(R3>255) R3=255; if(R3<0) R3=0;
//зеленый G
G3 = Y3[i][j] - 0.39465 * (U3[i][j] - 128) - 0.58060 * (V3[i][j] - 128);
if(G3>255) G3=255; if(G3<0) G3=0;
//синий B
B3 = Y3[i][j] + 2.03211 * (U3[i][j] - 128);
if(B3>255) B3=255; if(B3<0) B3=0;
//восстановление массива изображения
biImage1[j*4+2]= R3;
biImage1[j*4+1]= G3;
biImage1[j*4] = B3;
 }
}
//обновление изображения на экране
Form1->Image1->Refresh();

Результат встраивания ЦВЗ представлен на рисунке ниже.