W ramach dzisiejszych zajęć będziemy zajmować się dwoma zagadnieniami — Steganografią, czyli ukrywaniem informacji na obrazach oraz znakowaniem wodnym obrazów. W ramach zajęć dostajecie dostęp do 3 prostych funkcji realizujących te zagadnienia poruszane na dzisiejszych zajęciach:
Funkcja do znakowania wodnego. Przyjmuje jako parametry obraz, który będzie poddawany znakowaniu wodnemu, maskę o tych samych wymiarach co obraz, przechowującą dane w typie
boolen
lub jako obraz zwykły oraz parametralfa
służący do określania stopnia przesłaniania maski. Jeżeli wyniki będą zbyt prześwietlone należy zmienić wartość parametru gamma z linijki#21
z 0 na-alpha
.def water_mark(img,mask,alpha=0.25): assert (img.shape[0]==mask.shape[0]) and (img.shape[1]==mask.shape[1]), "Wrong size" if len(img.shape)<3: =True flag=cv2.cvtColor(img,cv2.COLOR_GRAY2RGBA) t_imgelse: =False flag=cv2.cvtColor(img,cv2.COLOR_RGB2RGBA) t_imgif (mask.dtype==bool): =cv2.cvtColor((mask*255).astype(np.uint8),cv2.COLOR_GRAY2RGBA) t_maskelif (mask.dtype==np.uint8): if len(mask.shape)<3: =cv2.cvtColor((mask).astype(np.uint8),cv2.COLOR_GRAY2RGBA) t_maskelse: =cv2.cvtColor((mask).astype(np.uint8),cv2.COLOR_RGB2RGBA) t_maskelse: if len(mask.shape)<3: =cv2.cvtColor((mask*255).astype(np.uint8),cv2.COLOR_GRAY2RGBA) t_maskelse: =cv2.cvtColor((mask*255).astype(np.uint8),cv2.COLOR_RGB2RGBA) t_mask=cv2.addWeighted(t_img,1,t_mask,alpha,0) t_outif flag: =cv2.cvtColor(t_out,cv2.COLOR_RGBA2GRAY) outelse: =cv2.cvtColor(t_out,cv2.COLOR_RGBA2RGB) outreturn out
Funkcja do kodowania informacji jednej warstwie obrazu. Jeżeli chcecie zakodować więcej informacji na obrazie, to do funkcji kodującej należy przekazywać informację dla każdej warstwy osobno. Funkcja jako parametry przyjmuje kolejno: warstwę obrazu, na której będzie kodowana informacja. Informację, jaka ma zostać zakodowana — może być dostarczona jako macierz
bool
o rozmiarze 1 warstwy obrazu lub dowolny inny wektor, lub tablicauint8
(np. łańcuch tekstowy), która zostanie zakodowana. Ostatni parametr to maska binarna w postaci liczbyuint8
określająca, na których bitach chcemy zapisywać. Wartości bitów oznaczają kolejne potęgi liczby \(2\). \(1 =2^0\) oznacza 1 bit, czyliLSB
, a \(12 = 2^2 + 2^3\) oznacza zapis na 3 i 4 bicie.def put_data(img,data,binary_mask=np.uint8(1)): assert img.dtype==np.uint8 , "img wrong data type" assert binary_mask.dtype==np.uint8, "binary_mask wrong data type" =np.unpackbits(binary_mask) un_binary_maskif data.dtype!=bool: =np.unpackbits(data) unpacked_dataelse: =data unpacked_data=img.shape[0]*img.shape[1]*np.sum(un_binary_mask) dataspaceassert (dataspace>=unpacked_data.size) , "too much data" if dataspace==unpacked_data.size: =unpacked_data.reshape(img.shape[0],img.shape[1],np.sum(un_binary_mask)).astype(np.uint8) prepered_dataelse: =np.resize(unpacked_data,(img.shape[0],img.shape[1],np.sum(un_binary_mask))).astype(np.uint8) prepered_data=np.full((img.shape[0],img.shape[1]),binary_mask) mask=np.bitwise_and(img,np.invert(mask)) img=0 bvfor i,b in enumerate(un_binary_mask[::-1]): if b: =prepered_data[:,:,bv] temp=np.left_shift(temp,i) temp=np.bitwise_or(img,temp) img+=1 bvreturn img
Funkcja odkodowująca informację z obrazu. Przyjmuje jako parametr warstwę z zakodowaną informacją oraz maskę binarną użytą przy kodowaniu. Dane zwracane są w formie wartości
bool
z każdej z warstw podanej w masce. Dodatkowy parametr rozmiaru pozwala odtworzyć dane w innym rozmiarze np. wektora z tekstem, jaki został zakodowany.def pop_data(img,binary_mask=np.uint8(1),out_shape=None): =np.unpackbits(binary_mask) un_binary_mask=np.zeros((img.shape[0],img.shape[1],np.sum(un_binary_mask))).astype(np.uint8) data=0 bvfor i,b in enumerate(un_binary_mask[::-1]): if b: =np.full((img.shape[0],img.shape[1]),2**i) mask=np.bitwise_and(img,mask) temp=temp[:,:].astype(np.uint8) data[:,:,bv]+=1 bvif out_shape!=None: =np.packbits(data.flatten()) tmp=tmp[:np.prod(out_shape)] tmp=tmp.reshape(out_shape) datareturn data
Zadania
Przygotuj zestaw danych testowych (0,20 pkt):
co najmniej dwa różne obrazy kolorowe ≈ 512 × 512 px (inne niż Lena),
jeden obraz binarny o tych samych wymiarach,
plik tekstowy zawierający dowolny cytat (≈ 200 znaków).
Za‑ i odkoduj plik tekstowy w najmłodszym bicie (LSB) kanału niebieskiego wybranego obrazu‑nosiciela (put_data, binary_mask = 1). Zweryfikuj poprawność odzyskanego tekstu (pop_data) oraz oblicz i podaj wartości PSNR / SSIM dla kanału B przed i po ukryciu danych (0,20 pkt).
Ukryj cały przygotowany obraz kolorowy w obrazie‑nosiciela, zapisując (0,20 pkt):
3 najmłodsze bity kanału B,
2 najmłodsze bity kanałów R i G.
Odzyskaj obraz (pop_data) i oceń jakościowo: podaj PSNR / SSIM osobno dla każdego kanału nosiciela i przedstaw wizualne porównanie „przed–po”.
Systematycznie zwiększaj liczbę zapisywanych bitów (łącznie w kanałach RGB) i określ minimalny „budżet bitowy”, przy którym degradacja obrazu‑nosiciela staje się zauważalna gołym okiem. Swoje spostrzeżenia uzasadnij wartościami PSNR / SSIM oraz zrzutami ekranu (0,20 pkt).
Nałóż na obraz‑nosiciela znak wodny (water_mark) w postaci przygotowanej binarnej maski (np. logo). Zbadaj wpływ wartości
α = 0.10, 0.25, 0.50
na (0,20 pkt):wartości PSNR / SSIM (dla całego obrazu),
widoczność znaku wodnego w praktyce.
Zaprezentuj wyniki w formie tabeli i krótkiego komentarza.
Do oddania
- Plik
PDF
ze sprawozdaniem zawierający wyniki, obserwacje i wnioski.
Przykłady wizualne pokazujące co można zrobić
Przykład dekonstrukcji warstwy obrazu na poszczególne wartości bitów przy użyciu funkcji np.bitwise_and
. Proces odwrotny do dekonstrukcji to pomnożenie wartości logicznych przez wartość bitu i dodanie do obrazu.
Przykład wykorzystania dekonstrukcji obrazu w praktyce na obrazie kolorowym.
Przykład ukrycia kolorowego obrazu wewnątrz innego.