ONA zadanie 3 – Steganografia

W naszym ostatnim i zarazem największym zadaniu zaliczeniowym odejdziemy od tematyki epidemiologicznej i zajmiemy się przetwarzaniem obrazów i ich kompresją. Konkretnie, naszym zadaniem będzie napisanie programu pozwalającego na “ukrywanie” informacji w obrazach, czyli steganografię. Wykorzystamy najprostszą metodę steganografii, czyli wykorzystanie modyfikacji najmniej znaczącego bitu do zakodowania “ukrytej informacji”.

Zasada działania:

Załóżmy, że mamy do przekazania komuś wektor bitów S, długości N  (może to być obraz, lub tekst – jest to dla nas w tej chwili bez znaczenia). Możemy wziąć jakiś obraz reprezentowany w standardowym formacie RGB, z 8 bitami na kanał i powiedzmy wymiarach 2000×1000 pikseli. Obraz ten, możemy wczytać do pamięci w postaci macierzy M o wymiarach 2000x1000x3 z typem danych int8uint8. Zauważmy, że każda z liczb w tej macierzy zawiera intensywność jednego z kolorów podstawowych w  postaci wartości od 0 do 255. Niewielkie zmiany tych wartości (np. o 1) nie są łatwo postrzegalne dla ludzkich oczu, ale program komputerowy może bardzo łatwo je wykryć.

Nasze zadanie będzie polegało na tym, że każdy kolejny bit z wektora S “wstawiać” jako ostatni bit kolejnego bajtu macierzy M. Jak go wstawiać? otóż wystarczy, że sprawdzimy czy reszta z dzielenia kolejnego bajtu macierzy M przez 2 jest równa wartości odpowiedniego bitu z wektora S.

M.reshape((2000*1000*3,))[i]%2 == S[i]

Jeśli tak, to nic nie musimy robić. Jeśli nie, to musimy odjąć lub dodać 1 do M.reshape((2000*1000*3,))[i], żeby nasze równanie było prawdziwe (uważając przy tym aby nie odjąć od zera ani nie dodawać do 255).

W ten sposób otrzymujemy nową macierz M, która ma tę własność, że ostatnie bity reprezentacji kolejnych bajtów M są równe kolejnym bitom S. Taki plik możemy zapisać jako obraz i będzie on bardzo subtelnie wizualnie zmieniony, natomiast odbiorca tego pliku, który wie czego szukać może bardzo łatwo odczytać wektor S

Aby rozwiązanie było praktyczne, musimy jeszcze być w stanie podać długość wektora S oraz typ danych do zapisu tego wektora. W naszym rozwiązaniu, pierwszy zakodowany bit (M[0]%2) będzie określał, czy mamy do czynienia z tekstem w alfabecie DNA (M[0]%2==0) czy z obrazem w skali szarości (M[0]%2==1). Kolejne 32 bity będą podawać nam długość ciągu DNA (w postaci jednej 32 bitowej całkowitej liczby dodatniej w kodzie dwójkowym) lub wymiary obrazka (w postaci dwóch 16bitowych całkowitych liczb dodatnich oznaczających odpowiednio szerokość x i wysokość y zakodowanego obrazka).

Zadanie: 

Napisz moduł pythonowy (z komentarzami!) o nazwie stegano.py,  który realizuje następujące funkcje:

  1. read_image_greyscale (image_file) – zwracający wymiary obrazka oraz ciąg bitów wartości na podstawie pliku w formacie png (2 pkt)
  2. write_image_greyscale (x,y,bit_vector,output_image_file) – zapisujący obrazek do formatu png w skali szarości (2pkt)
  3. read_DNA(text_file) – zwracający ciąg bitów na podstawie pliku tekstowego zawierającego wyłącznie alfabet ACGT (kodowanie bitowe: A=00 ,C=01, G=10, T=11) (2 pkt)
  4. write_DNA(bit_vector,output_text_file) – zapisuje ciąg liter DNA do pliku tekstowego na podstawie ciągu bitów wg powyższego kodowania (2 pkt)
  5. encode_DNA(bit_vector,image_file, output_file) – wczytujący obraz RGB z pliku image file i zakodowujący wektor bit_vector reprezentujący DNA w ostatnich bitach obrazu (zgodnie z powyższym opisem, łącznie z 33 bitami nagłówka). Zmodyfikowany obraz powinien być zapisany do pliku output_file. Jeśli obraz jest za mały aby zakodować cały wektor, program powinien zwrócić błąd. (5 pkt)
  6. encode_image(bit_vector,x,y,image_file,output_file) - wczytujący obraz RGB z pliku image file i zakodowujący wektor bit_vector reprezentujący obraz w ostatnich bitach obrazu (zgodnie z powyższym opisem, łącznie z 33 bitami nagłówka). Zmodyfikowany obraz powinien być zapisany do pliku output_file. Jeśli obraz jest za mały aby zakodować cały wektor, program powinien zwrócić błąd. (5 pkt)
  7. decode(image_file, output_file) – powinien wczytywać obraz RGB z pliku, odczytywać z nagłówka (33 ostatnie bity z pierwszych 33 bajtów) informację o tym, czy zakodowane jest DNA, czy obraz, następnie odkodowywać informację i zapisać (korzystając z write_DNA lub write_image_grayscale) wynik do pliku output_file. (5 pkt)
  8. main(), która powinna umożliwiać zakodowywanie i odkodowywanie informacji przy pomocy parametrów z linii komend obsługiwanych przy pomocy modułu Argparse. (2 pkt)

Terminy:

Rozwiązania (w postaci e-maila z normalnym załącznikami w postaci pliku stegano.py  oraz zakodowanych przykładowych danych i dopiskiem [ONA-3] w temacie) w ciągu 3 tygodni od dzisiaj, czyli do 18. VI 2020

Przykładowe dane:

Można popróbować np. zakodować taki obrazek:

Coronaviruses_004_lores

lub kod DNA koronawirusa covid-19.fasta

W jakimś ulubionym pliku ze zdjęciem przedstawiającym swoją fizjonomię.

Wersja bonusowa (+5 pkt)

Można wyposażyć nasz program w dodatkową funkcjonalność polegającą na kompresji ciągu bitów przed zakodowaniem. Jeśli ktoś zaimplementuje kompresję (i dekompresję) metodą LZ77 to może otrzymać dodatkowe 5 punktów).

15 thoughts on “ONA zadanie 3 – Steganografia

  1. Krzysztof

    Czy musimy trzymać się dokładnie tego schematu funkcji, który Pan podał, czy można na przykład zamiast dwóch osobnych funkcji encode zrobić jedną wspólną i napisać funkcje read w taki sposób, żeby zwracały wektor z zawartymi już 33 bitami nagłówka?

    Reply
    1. bartek Post author

      Zasadniczo istotna jest funkcjonalność, może Pan połączyć funkcje encode w jedną, ale proszę odnotować to w komentarzu.

      Reply
  2. Paulina Kucharewicz

    “Obraz ten, możemy wczytać do pamięci w postaci macierzy M o wymiarach 2000x1000x3 z typem danych int8. Zauważmy, że każda z liczb w tej macierzy zawiera intensywność jednego z kolorów podstawowych w postaci wartości od 0 do 255.”

    Czy mamy użyć typ uint8?

    Reply
  3. bartek Post author

    Ponieważ były w tej sprawie pytania – precyzuję. Ostatecznym terminem, w którym można uzyskać pełne punkty za program 3 to czwartek, 18 VI do godziny 23:59.

    Reply
  4. Weronika

    Czy w funkcji read_DNA mamy założyć, że plik tekstowy będzie miał pierwszy wiersz niebędący fragmentem kodu DNA jak w pliku przykładowym, czy raczej powinniśmy obrobić plik przykładowy do testów?

    Reply
    1. bartek Post author

      Pliki FASTA (takie jak w przykładzie) są bardzo popularne w przekazywaniu sekwencji DNA, więc warto obsłużyć taki format. Wymaganie pliku zawierającego wyłącznie symbole DNA też jest dopuszczalne, ale proszę to zaznaczyć w komentarzach.

      Reply
  5. Weronika

    Czy nasz wektor bitów wartości powinien mieć dtype ‘bool_’ czy wystarczy, że jest ciągiem zer i jedynek?

    Reply
    1. bartek Post author

      Moim zdaniem wygodniej jeśli to będzie Bool, ale jeśli Pani woli zera i jedynki, to OK. Ważne, żeby działało.

      Reply
  6. Weronika

    Mam wrażenie, że w obecnym systemie kodowania wektorów dekodowanie przy kompresji nie byłyby możliwe. O ile dobrze rozumiem bity 1-33 przy kodowaniu DNA mają symbolizować długość tego kodu (ilość literek), a przy kodowaniu obrazka jego wymiary już w postaci macierzowej. Stanowią one klucz do określenia jak dużo bitów z obrazu wczytanego mamy dekodować. Wydaje mi się, że liczba ta po kompresji nie będzie zależna w stały sposób od liczb z prefiksu.
    Czy przy obecnym zapisie dekodowanie skompresowanego ciągu bitów jest możliwe?

    Reply
    1. bartek Post author

      Nie rozumiem jaki Pani widzi problem. Podczas dekodowania najpierw odczytuje Pani pierwszy bit. Na jego podstawie wie już Pani, czykolejne 32 bity to jedna, czy dwie liczby. A na podstawie tej liczb(y) może Pani dekodować resztę.

      Reply
      1. Weronika

        Mówię tu o sytuacji z wersji bonusowej. Jak kompresujemy plik to zmieni on długość. Obecny nagłówek mówi o wielkości pełnego pliku. Jeśli będziemy odczytywać np greyscale_image o oryginalnej wielkości 729×1024 z jakiegoś zdjęcia, to w sytuacji podstawowej mielibyśmy 729*1024*8 bitów długości. Tą informację możemy wyczytać z nagłówka. W wersji dodatkowej musimy odczytać plik skompresowany, który z założenia będzie krótszy, ale to jak bardzo krótszy zależy od powtarzalności ciągów bitów a nie wielkości oryginalnego obrazka. Czy jeśli chcemy więc zrobić wersję dodatkową to czy należy jakoś zmienić nagłówek tak, aby zawierał długość pliku skompresowanego?

        Reply
        1. bartek Post author

          Ach, przepraszam, nie zrozumiałem Pani. Niemniej nadal problemu technicznego nie ma. Ma Pani strumień bitów i musi Pani zdekompresować określoną liczbę bitów. Po prostu czyta Pani tak długo, aż zdekompresowany ciąg jest odpowiedniej długości. Rzeczywiście mogłoby być łatwiej, gdyby nagłówek zawierał długość ciągu skompresowanego, ale nie jest to niemożliwe bez tego. Odpowiadając na Pani pytanie – nie musi Pani zmieniać formatu nagłówka, ale może to Pani zrobić, jeśli uważa Pani, że tak będzie lepiej.

          Reply

Leave a Reply to Krzysztof Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>