diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/Ambadekar2019.py b/Ambadekar2019.py new file mode 100644 index 0000000..cd7721c --- /dev/null +++ b/Ambadekar2019.py @@ -0,0 +1,34 @@ +""" +Ambadekar, S. P., Jain, J., & Khanapuri, J. (2019). +Digital Image Watermarking Through Encryption and DWT for Copyright Protection. +In Advances in Intelligent Systems and Computing (Vol. 727, pp. 187–195). Springer Singapore. +https://doi.org/10.1007/978-981-10-8863-6_19""" + +from pywt import dwt2, idwt2 + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 1 + + def __init__(self, scale_factor=2**-6) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + self.LL, (self.HL, self.LH, self.HH) = dwt2(host, wavelet='haar') + LL_w, (HL_w, LH_w, HH_w) = dwt2(watermark, wavelet='haar') + + LL_ = self.LL*(1-self.sf) + LL_w*self.sf + HL_ = self.HL*(1-self.sf) + HL_w*self.sf + LH_ = self.LH*(1-self.sf) + LH_w*self.sf + HH_ = self.HH*(1-self.sf) + HH_w*self.sf + + return idwt2((LL_, (HL_, LH_, HH_)), wavelet='haar') + + def extract_watermark(self, watermarked_image): + LL_, (HL_, LH_, HH_) = dwt2(watermarked_image, wavelet='haar') + return idwt2(( + (LL_-self.LL*(1-self.sf))/self.sf, + ((HL_-self.HL*(1-self.sf))/self.sf, + (LH_-self.LH*(1-self.sf))/self.sf, + (HH_-self.HH*(1-self.sf))/self.sf,), + ), wavelet='haar') diff --git a/Chandra2002.py b/Chandra2002.py new file mode 100644 index 0000000..33a89e0 --- /dev/null +++ b/Chandra2002.py @@ -0,0 +1,36 @@ +""" +Chandra, D. V. S. (2010). +Digital image watermarking using singular value decomposition. +The 2002 45th Midwest Symposium on Circuits and Systems, 2002. MWSCAS-2002., 3(3), III-264-III–267. +https://doi.org/10.1109/MWSCAS.2002.1187023 +""" + +# from numpy.linalg import svd +from scipy.linalg import svd, diagsvd + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 1 + + def __init__(self, scale_factor=2**(-4)) -> None: + """Initiates watermarker object + + Args: + scale_factor (float or array of floats, optional): + Simple Scale Factor if the scale_factor is a float . + Multiple Scale Factor if the scale_factor is an array of length k, + where k is the number of singular values. + Defaults to 2**(-4) + """ + self.sf = scale_factor + + def add_watermark(self, host, watermark): + U, self.s, Vh = svd(host, full_matrices=True) + self.U_w, s_w, self.Vh_w = svd(watermark, full_matrices=True) + s = self.s + self.sf * s_w + return U @ diagsvd(s, *watermark.shape) @ Vh + + def extract_watermark(self, watermarked): + s = svd(watermarked, compute_uv=False) + s_w = (s - self.s) / self.sf + return self.U_w @ diagsvd(s_w, *watermarked.shape) @ self.Vh_w diff --git a/Chang2005.py b/Chang2005.py new file mode 100644 index 0000000..53d9906 --- /dev/null +++ b/Chang2005.py @@ -0,0 +1,66 @@ +""" +Chang, C. C., Tsai, P., & Lin, C. C. (2005). +SVD-based digital image watermarking scheme. +Pattern Recognition Letters, 26(10), 1577–1586. +https://doi.org/10.1016/j.patrec.2005.01.004 +""" + +from numpy import abs, angle, empty, empty_like, exp, sign +from numpy.linalg import svd + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 4 + + def __init__(self, scale_factor=.012) -> None: + self.sf = scale_factor + + def add_watermark(self, host_image, watermark_image): + self.R1 = host_image.shape[0] // watermark_image.shape[0] + self.R2 = host_image.shape[1] // watermark_image.shape[1] + + watermarked = empty_like(host_image) + for i in range(0, watermark_image.shape[0]): + for j in range(0, watermark_image.shape[1]): + U, s, Vh = svd(host_image[i * self.R1:(i + 1) * self.R1, + j * self.R2:(j + 1) * self.R2], + full_matrices=False) + # complexity = s[s != 0].size / s.size + + m = (abs(U[1, 0]) + abs(U[2, 0])) / 2 + d = abs(U[1, 0]) - abs(U[2, 0]) + + if watermark_image[i, j] < .5: + if d < self.sf: + U[1, 0] = (m + self.sf / 2)*exp(angle(U[1, 0])*1j) + U[2, 0] = (m - self.sf / 2)*exp(angle(U[2, 0])*1j) + + else: + if -d < self.sf: + U[1, 0] = (m - self.sf / 2)*exp(angle(U[1, 0])*1j) + U[2, 0] = (m + self.sf / 2)*exp(angle(U[2, 0])*1j) + + watermarked[i * self.R1:(i + 1) * self.R1, + j * self.R2:(j + 1) * self.R2] = (U * s) @ Vh + + return watermarked + + def extract_watermark(self, watermarked_image): + R = self.IMAGE_TO_WATERMARK_RATIO + watermark = empty(shape=(watermarked_image.shape[0] // R, + watermarked_image.shape[1] // R)) + for i in range(0, watermark.shape[0]): + for j in range(0, watermark.shape[1]): + U, s, Vh = svd( + watermarked_image[i * self.R1:(i + 1) * self.R1, + j * self.R2:(j + 1) * self.R2]) + # complexity = s[s != 0].size / s.size + + positive = abs(U[2, 0]) - abs(U[1, 0]) >= self.sf / 2 + + # negative = abs(U[1, 0]) - abs(U[2, 0]) >= self.sf / 2 + # assert positive or negative + + watermark[i, j] = positive + + return watermark diff --git a/Ganic2004.py b/Ganic2004.py new file mode 100644 index 0000000..811d8bf --- /dev/null +++ b/Ganic2004.py @@ -0,0 +1,48 @@ +""" +Ganic, E., & Eskicioglu, A. M. (2004). +Robust DWT-SVD domain image watermarking. +In Proceedings of the 2004 multimedia and security workshop on Multimedia and security - MM&Sec ’04 (p. 166). New York, New York, USA: ACM Press. +https://doi.org/10.1145/1022431.1022461 +""" + +from pywt import dwt2, idwt2 + +from Chandra2002 import Watermarker as chandraWatermarker + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 2 + + def __init__(self, scale_factor=.05) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + LL, (HL, LH, HH) = dwt2(host, wavelet='haar') + + self.svd_watermarker_1 = chandraWatermarker(self.sf) + self.svd_watermarker_2 = chandraWatermarker(self.sf / 50) + self.svd_watermarker_3 = chandraWatermarker(self.sf / 50) + self.svd_watermarker_4 = chandraWatermarker(self.sf / 50) + + LL_ = self.svd_watermarker_1.add_watermark(LL, watermark) + HL_ = self.svd_watermarker_2.add_watermark(HL, watermark) + LH_ = self.svd_watermarker_3.add_watermark(LH, watermark) + HH_ = self.svd_watermarker_4.add_watermark(HH, watermark) + + return idwt2((LL_, (HL_, LH_, HH_)), wavelet='haar') + + def extract_watermark(self, watermarked_image): + LL, (HL, LH, HH) = dwt2(watermarked_image, wavelet='haar') + return self.svd_watermarker_1.extract_watermark(LL) + return self.svd_watermarker_2.extract_watermark(HL) + return self.svd_watermarker_3.extract_watermark(LH) + return self.svd_watermarker_4.extract_watermark(HH) + + def extract_watermarks(self, watermarked_image): + LL, (HL, LH, HH) = dwt2(watermarked_image, wavelet='haar') + return ( + self.svd_watermarker_1.extract_watermark(LL), + self.svd_watermarker_2.extract_watermark(HL), + self.svd_watermarker_3.extract_watermark(LH), + self.svd_watermarker_4.extract_watermark(HH), + ) diff --git a/Guo2014.py b/Guo2014.py new file mode 100644 index 0000000..6787a53 --- /dev/null +++ b/Guo2014.py @@ -0,0 +1,51 @@ +""" +Guo, J.-M. M., & Prasetyo, H. (2014). +False-positive-free SVD-based image watermarking. +Journal of Visual Communication and Image Representation, 25(5), 1149–1163. +https://doi.org/10.1016/j.jvcir.2014.03.012 +""" + +from numpy import empty, empty_like +from scipy.linalg import diagsvd, svd +# from numpy.linalg import svd + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 4 + + def __init__(self, scale_factor=.01) -> None: + self.sf = scale_factor + + def add_watermark(self, host_image, watermark_image): + R = host_image.shape[0] // watermark_image.shape[0] + # R = host_image.shape[1] // watermark_image.shape[1] + + U_w, s_w, self.Vh_w = svd(watermark_image, full_matrices=True) + P_w = U_w @ diagsvd(s_w, *watermark_image.shape) + + watermarked = empty_like(host_image) + self.s = empty_like(watermark_image) + for i in range(0, watermark_image.shape[0]): + for j in range(0, watermark_image.shape[1]): + U, s, Vh = svd(host_image[i * R:(i + 1) * R, + j * R:(j + 1) * R], + full_matrices=False) + self.s[i, j] = s[0] + s[0] += self.sf * P_w[i, j] + watermarked[i * R:(i + 1) * R, + j * R:(j + 1) * R] = (U * s) @ Vh + + return watermarked + + def extract_watermark(self, watermarked_image): + R = self.IMAGE_TO_WATERMARK_RATIO + P_w = empty(shape=(watermarked_image.shape[0] // R, + watermarked_image.shape[1] // R)) + for i in range(0, P_w.shape[0]): + for j in range(0, P_w.shape[1]): + s = svd(watermarked_image[i * R:(i + 1) * R, + j * R:(j + 1) * R], + compute_uv=False) + P_w[i, j] = (s[0] - self.s[i, j]) / self.sf + return P_w @ self.Vh_w + diff --git a/Gupta2012.py b/Gupta2012.py new file mode 100644 index 0000000..1bd62d9 --- /dev/null +++ b/Gupta2012.py @@ -0,0 +1,29 @@ +""" +Lagzian, S., Soryani, M., & Fathy, M. (2011). +A New Robust Watermarking Scheme Based on RDWT-SVD. +International Journal of Intelligent Information Processing, 2(1), 22–29. +https://doi.org/10.4156/ijiip.vol2.issue1.3 +""" + +# from numpy.linalg import svd +from scipy.linalg import diagsvd, svd +from pywt import dwt2, idwt2 + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 2 + + def __init__(self, scale_factor=.02) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + LL, H = dwt2(host, wavelet='haar') + self.U, s, self.Vh = svd(LL, full_matrices=False) + self.U_w, s_w, self.Vh_w = svd(watermark, full_matrices=False) + LL_ = self.U * s_w @ self.Vh + return idwt2((LL_, H), wavelet='haar') + + def extract_watermark(self, watermarked_image): + LL, H = dwt2(watermarked_image, wavelet='haar') + s_w_ = svd(LL, compute_uv=False) + return self.U_w * s_w_ @ self.Vh_w diff --git a/Jain2008.py b/Jain2008.py new file mode 100644 index 0000000..c1ccb2e --- /dev/null +++ b/Jain2008.py @@ -0,0 +1,29 @@ +""" +Jain, C., Arora, S., & Panigrahi, P. K. (2008). +A Reliable SVD based Watermarking Schem. May 2018, 1–8. +preprint +http://arxiv.org/abs/0808.0309 +""" + +# from numpy.linalg import svd +from scipy.linalg import diagsvd, svd + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 1 + + def __init__(self, scale_factor=.02) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + self.host = host + w, h = host.shape + self.U, s, self.Vh = svd(self.host, full_matrices=True) + U_w, s_w, self.Vh_w = svd(watermark, full_matrices=True) + P_w = U_w @ diagsvd(s_w, *watermark.shape) + D = diagsvd(s, w, h) + self.sf * P_w + return self.U @ D @ self.Vh + + def extract_watermark(self, watermarked): + D = watermarked - self.host + return (self.U.T.conj() @ D @ self.Vh.T.conj()) / self.sf @ self.Vh_w diff --git a/Jane2014.py b/Jane2014.py new file mode 100644 index 0000000..41b5424 --- /dev/null +++ b/Jane2014.py @@ -0,0 +1,30 @@ +""" +Jane, O., Elbaşi, E., & İlk, H. G. (2014). +Hybrid Non-Blind Watermarking Based on DWT and SVD. +Journal of Applied Research and Technology, 12(4), 750–761. +https://doi.org/10.1016/S1665-6423(14)70091-4 +""" + +from pywt import dwt2, idwt2 + +from Liu2002 import Watermarker as Liu2002Watermarker + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 2 + + def __init__(self, scale_factor=.05) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + self.svd_watermarker = Liu2002Watermarker(self.sf) + + LL, (HL, LH, HH) = dwt2(host, wavelet='haar') + + LL_ = self.svd_watermarker.add_watermark(LL, watermark) + + return idwt2((LL_, (HL, LH, HH)), wavelet='haar') + + def extract_watermark(self, watermarked_image): + LL, (HL, LH, HH) = dwt2(watermarked_image, wavelet='haar') + return self.svd_watermarker.extract_watermark(LL) diff --git a/Lai2010.py b/Lai2010.py new file mode 100644 index 0000000..294e7af --- /dev/null +++ b/Lai2010.py @@ -0,0 +1,52 @@ +"""Lai, C.-C., & Tsai, C.-C. (2010). +Digital Image Watermarking Using Discrete Wavelet Transform and Singular Value Decomposition. +IEEE Transactions on Instrumentation and Measurement, 59(11), 3060–3063. +https://doi.org/10.1109/TIM.2010.2066770 +""" + +from pywt import dwt2, idwt2 + +from Liu2002 import Watermarker as Liu2002Watermarker + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 2 + + def __init__(self, scale_factor=.01) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + # 1) Use one-level Haar DWT to decompose the cover imageAinto four subbands (i.e., LL, LH, HL, and HH). + LL, (HL, LH, HH) = dwt2(host, wavelet='haar') + + # 2) Apply SVD to LH and HL subbands, i.e.,Ak=UkSkVkT,k=1,2(1)wherekrepresents one of two subbands. + self.svd_watermarker_1 = Liu2002Watermarker(self.sf) + self.svd_watermarker_2 = Liu2002Watermarker(self.sf) + + # 3) Divide the watermark into two parts: W=W_1+W_2,where W_k denotes half of the watermark. + watermark_1 = watermark.copy() + watermark_1[watermark > watermark.mean()] = 0 + watermark_2 = watermark - watermark_1 + + # 4) Modify the singular values in HL and LH subbands with half of the watermark image and then apply SVD to them,respectively, + # 5) Obtain the two sets of modified DWT coefficients, i.e.,A∗k=UkSkWVkT,k=1,2.(3) + LH_ = self.svd_watermarker_1.add_watermark(LH, watermark_1) + HL_ = self.svd_watermarker_2.add_watermark(HL, watermark_2) + + # 6) Obtain the watermarked imageAWby performing the in-verse DWT using two sets of modified DWT coefficients andtwo sets of nonmodified DWT coefficients. + + return idwt2((LL, (HL_, LH_, HH)), wavelet='haar') + + def extract_watermark(self, watermarked_image): + # 1) Use one-level Haar DWT to decompose the watermarked(possibly distorted) imageA∗Winto four subbands: LL, LH,HL, and HH. + LL, (HL_, LH_, HH) = dwt2(watermarked_image, wavelet='haar') + + # 2) Apply SVD to the LH and HL subbands, i.e.,A∗kW=U∗kS∗kWV∗kT,k=1,2(4)wherekrepresents one of two subbands. + # 3) Compute D∗k=UkWS∗kWVkTW,k=1,2. + # 4) Extract half of the watermark image from each subband, i.e.,W∗k=(D∗k−Sk)/α,k=1,2.(5) + + watermark_image_1 = self.svd_watermarker_1.extract_watermark(LH_) + watermark_image_2 = self.svd_watermarker_2.extract_watermark(HL_) + + # 5) Combine the results of Step 4 to obtain the embedded water-mark:W∗=W∗1+W∗2. + return watermark_image_1 + watermark_image_2 diff --git a/Liu2002.py b/Liu2002.py new file mode 100644 index 0000000..9c0d0d7 --- /dev/null +++ b/Liu2002.py @@ -0,0 +1,38 @@ +""" +Liu, R., & Tan, T. (2002). +An SVD-based watermarking scheme for protecting rightful ownership. +IEEE Transactions on Multimedia, 4(1), 121–128. +https://doi.org/10.1109/6046.985560 +""" + +# from numpy.linalg import svd +from scipy.linalg import diagsvd, svd + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 1 + + def __init__(self, scale_factor=.1) -> None: + """Initiates watermarker object + + Args: + scale_factor (float or array of floats, optional): + Simple Scale Factor if the scale_factor is a float . + Multiple Scale Factor if the scale_factor is an array of length k, + where k is the number of singular values. + Defaults to 0.1 + """ + self.sf = scale_factor + + def add_watermark(self, host, watermark): + U, s, Vh = svd(host, full_matrices=False) + self.S = diagsvd(s, *host.shape[:2]) + D = self.S + watermark * self.sf + self.U_w, s_w, self.Vh_w = svd(D, full_matrices=False) + return (U * s_w) @ Vh + + def extract_watermark(self, watermarked_image): + s_w = svd(watermarked_image, compute_uv=False) + k = min(watermarked_image.shape[:2]) + D = (self.U_w * s_w) @ self.Vh_w + return (D - self.S) / self.sf diff --git a/Liu2019.py b/Liu2019.py new file mode 100644 index 0000000..1e45f2e --- /dev/null +++ b/Liu2019.py @@ -0,0 +1,68 @@ +""" +Liu, J., Huang, J., Luo, Y., Cao, L., Yang, S., Wei, D., & Zhou, R. (2019). +An Optimized Image Watermarking Method Based on HD and SVD in DWT Domain. +IEEE Access, 7, 80849–80860. +https://doi.org/10.1109/ACCESS.2019.2915596 +""" + +from numpy import log2 +from pywt import wavedec2, waverec2 +from scipy.linalg import hessenberg + +from Chandra2002 import Watermarker as chandraWatermarker + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 2 + + def __init__(self, scale_factor=2**(-4)) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + self.svd_watermarker = chandraWatermarker(self.sf) + + # Step 1. Based on R-level DWT,C is decomposed into the components of LL,LH,HL,HH, where R=log_2(M/N) + self.level = int(log2(host.shape[0] // watermark.shape[0])) + LL, *HH = wavedec2(host, wavelet='haar', level=self.level) + + # Step 2. HD is performed on LL + H, P = hessenberg(LL, calc_q=True) + + H_ = self.svd_watermarker.add_watermark(H, watermark) + + # # Step 3. Apply SVD to H + # HU, self.Hs, HVh = svd(H) + + # # Step 4. W is applied with SVD + # self.U_w, s_w, self.Vh_w = svd(watermark_image) + + # # Step 5. Compute an embedded singular value HS_w* by adding HS_w and S_w with a scaling factor α + # Hs_w_ = self.Hs + self.sf * s_w + + # # Step 6. The watermarked sub-band H∗ is generated byusing the inverse SVD + # H_ = HU @ diag(Hs_w_) @ HVh + + # Step 7. A new low-frequency approximate sub-band LL∗ is reconstructed based on the inverse HD which is given by + LL_ = P @ H_ @ P.T.conj() + + # Step 8. The watermarked image C∗ is obtained by performing the inverse R-level DWT + return waverec2((LL_, *HH), wavelet='haar') + + def extract_watermark(self, watermarked_image): + + # Step 1.The watermarked host image C∗ is decomposedinto four sub-bands by R-level DWT + LL_, *_ = wavedec2(watermarked_image, wavelet='haar', level=self.level) + + # Step 2.HD is performed on LL + H_ = hessenberg(LL_) + + return self.svd_watermarker.extract_watermark(H_) + + # # Step 3.Apply SVD to H + # Hs_w_ = svd(H_, compute_uv=False) + + # # Step 4.The extracted singular value S_w* is gained by + # s_w = (Hs_w_ - self.Hs) / self.sf + + # # Step 5.The extracted watermark W∗ is reconstructed by inverse SVD + # return self.U_w @ diag(s_w) @ self.Vh_w diff --git a/Mohammad2008.py b/Mohammad2008.py new file mode 100644 index 0000000..260caf5 --- /dev/null +++ b/Mohammad2008.py @@ -0,0 +1,26 @@ +""" +Mohammad, A. A., Alhaj, A., & Shaltaf, S. (2008). +An improved SVD-based watermarking scheme for protecting rightful ownership. +Signal Processing, 88(9), 2158–2180. +https://doi.org/10.1016/j.sigpro.2008.02.015 +""" + +# from numpy.linalg import svd +from scipy.linalg import diagsvd, svd + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 1 + + def __init__(self, scale_factor=.02) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + self.U, s, self.Vh = svd(host, full_matrices=True) + self.S = diagsvd(s, *host.shape[:2]) + D = self.S + watermark * self.sf + return self.U @ D @ self.Vh + + def extract_watermark(self, watermarked_image): + D = self.U.T.conj() @ watermarked_image @ self.Vh.T.conj() + return (D - self.S) / self.sf diff --git a/Niu2016.py b/Niu2016.py new file mode 100644 index 0000000..c88a379 --- /dev/null +++ b/Niu2016.py @@ -0,0 +1,31 @@ +""" +Niu, Y., Cui, X., Li, Q., & Ding, J. (2016). +A SVD-Based Color Image Watermark Algorithm in DWT Domain +Advanced Graphic Communications, Packaging Technology and Materials (pp. 303–309). +https://doi.org/10.1007/978-981-10-0072-0_39 +""" + +from pywt import dwt2, idwt2 + +from Chandra2002 import Watermarker as chandraWatermark + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 2 + + def __init__(self, scale_factor=2**(-4)) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + LL, (HL, LH, HH) = dwt2(host, wavelet='haar') + + self.svd_watermarker = chandraWatermark(self.sf) + + LL_ = self.svd_watermarker.add_watermark(LL, watermark) + + return idwt2((LL_, (HL, LH, HH)), 'haar') + + def extract_watermark(self, watermarked_image): + LL, (HL, LH, HH) = dwt2(watermarked_image, wavelet='haar') + + return self.svd_watermarker.extract_watermark(LL) diff --git a/Run2012.py b/Run2012.py new file mode 100644 index 0000000..dbb5ae9 --- /dev/null +++ b/Run2012.py @@ -0,0 +1,48 @@ +""" +Ganic, E. (2005). +Robust embedding of visual watermarks using discrete wavelet transform and singular value decomposition. +Journal of Electronic Imaging, 14(4), 043004. +https://doi.org/10.1117/1.2137650 +""" + +from pywt import dwt2, idwt2 + +from Jain2008 import Watermarker as JainWatermarker + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 2 + + def __init__(self, scale_factor=.05) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + LL, (HL, LH, HH) = dwt2(host, wavelet='haar') + + self.svd_watermarker_1 = JainWatermarker(self.sf) + self.svd_watermarker_2 = JainWatermarker(self.sf / 10) + self.svd_watermarker_3 = JainWatermarker(self.sf / 10) + self.svd_watermarker_4 = JainWatermarker(self.sf / 10) + + LL_ = self.svd_watermarker_1.add_watermark(LL, watermark) + HL_ = self.svd_watermarker_2.add_watermark(HL, watermark) + LH_ = self.svd_watermarker_3.add_watermark(LH, watermark) + HH_ = self.svd_watermarker_4.add_watermark(HH, watermark) + + return idwt2((LL_, (HL_, LH_, HH_)), wavelet='haar') + + def extract_watermark(self, watermarked_image): + LL, (HL, LH, HH) = dwt2(watermarked_image, wavelet='haar') + return self.svd_watermarker_1.extract_watermark(LL) + return self.svd_watermarker_2.extract_watermark(HL) + return self.svd_watermarker_3.extract_watermark(LH) + return self.svd_watermarker_4.extract_watermark(HH) + + def extract_watermarks(self, watermarked_image): + LL, (HL, LH, HH) = dwt2(watermarked_image, wavelet='haar') + return ( + self.svd_watermarker_1.extract_watermark(LL), + self.svd_watermarker_2.extract_watermark(HL), + self.svd_watermarker_3.extract_watermark(LH), + self.svd_watermarker_4.extract_watermark(HH), + ) diff --git a/Sun2002.py b/Sun2002.py new file mode 100644 index 0000000..6bfc49d --- /dev/null +++ b/Sun2002.py @@ -0,0 +1,55 @@ +""" +Sun, R., Sun, H., & Yao, T. (2002). +A SVD and quantization based semi-fragile watermarking technique for image authentication. +International Conference on Signal Processing Proceedings, ICSP, 2, 1592–1595. +https://doi.org/10.1109/ICOSP.2002.1180102 +""" + +from numpy import empty, empty_like, mod +from numpy.linalg import svd + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 4 + + def __init__(self, scale_factor=2**(-4)) -> None: + self.sf = scale_factor + + def add_watermark(self, host_image, watermark_image): + self.R1 = host_image.shape[0] // watermark_image.shape[0] + self.R2 = host_image.shape[1] // watermark_image.shape[1] + watermarked = empty_like(host_image) + for i in range(0, watermark_image.shape[0]): + for j in range(0, watermark_image.shape[1]): + U, s, Vh = svd(host_image[i * self.R1:(i + 1) * self.R1, + j * self.R2:(j + 1) * self.R2], + full_matrices=False) + z = mod(s[0], self.sf) + if watermark_image[i, j] < .5: + if z >= self.sf * 3 / 4: + s[0] += -z + self.sf * 5 / 4 + else: + s[0] += -z + self.sf / 4 + else: + if z >= self.sf / 4: + s[0] += -z + self.sf * 3 / 4 + else: + s[0] += -z - self.sf / 4 + + watermarked[i * self.R1:(i + 1) * self.R1, + j * self.R2:(j + 1) * self.R2] = (U * s) @ Vh + + return watermarked + + def extract_watermark(self, watermarked_image): + # R = self.IMAGE_TO_WATERMARK_RATIO + watermark = empty(shape=(watermarked_image.shape[0] // self.R1, + watermarked_image.shape[1] // self.R2)) + for i in range(0, watermark.shape[0]): + for j in range(0, watermark.shape[1]): + s = svd(watermarked_image[i * self.R1:(i + 1) * self.R1, + j * self.R2:(j + 1) * self.R2], + compute_uv=False) + z = mod(s[0], self.sf) + watermark[i, j] = z > self.sf / 2 + return watermark diff --git a/Xing2007.py b/Xing2007.py new file mode 100644 index 0000000..c2613a5 --- /dev/null +++ b/Xing2007.py @@ -0,0 +1,46 @@ +""" +Xing, Y., & Tan, J. (2007). +A Color Watermarking Scheme Based on Block-SVD and Arnold Transformation. +Second Workshop on Digital Media and Its Application in Museum & Heritages (DMAMH 2007), 3–8. +https://doi.org/10.1109/DMAMH.2007.15 +""" + +from numpy import empty, empty_like +from numpy.linalg import svd + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 4 + + def __init__(self, scale_factor=2**(-4)) -> None: + self.sf = scale_factor + + def add_watermark(self, host_image, watermark_image): + self.R1 = host_image.shape[0] // watermark_image.shape[0] + self.R2 = host_image.shape[1] // watermark_image.shape[1] + + watermarked = empty_like(host_image) + self.s = empty_like(watermark_image) + for i in range(0, watermark_image.shape[0]): + for j in range(0, watermark_image.shape[1]): + U, s, Vh = svd(host_image[i * self.R1:(i + 1) * self.R1, + j * self.R2:(j + 1) * self.R2], + full_matrices=False) + self.s[i, j] = s[0] + s[0] += self.sf * watermark_image[i, j] + watermarked[i * self.R1:(i + 1) * self.R1, + j * self.R2:(j + 1) * self.R2] = (U * s) @ Vh + + return watermarked + + def extract_watermark(self, watermarked_image): + R = self.IMAGE_TO_WATERMARK_RATIO + watermark = empty(shape=(watermarked_image.shape[0] // R, + watermarked_image.shape[1] // R)) + for i in range(0, watermark.shape[0]): + for j in range(0, watermark.shape[1]): + s = svd(watermarked_image[i * self.R1:(i + 1) * self.R1, + j * self.R2:(j + 1) * self.R2], + compute_uv=False) + watermark[i, j] = (s[0] - self.s[i, j]) / self.sf + return watermark diff --git a/Yavuz2006.py b/Yavuz2006.py new file mode 100644 index 0000000..7bb42fa --- /dev/null +++ b/Yavuz2006.py @@ -0,0 +1,63 @@ +""" +Yavuz, E., & Telatar, Z. (2007). +Improved SVD-DWT based digital image watermarking against watermark ambiguity. +Proceedings of the 2007 ACM Symposium on Applied Computing - SAC ’07, 1051. +https://doi.org/10.1145/1244002.1244232 +""" + +from numpy import empty, empty_like +from scipy.fft import dctn, idctn + +from Chandra2002 import Watermarker as chandraWatermarker + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 8 + + def __init__(self, scale_factor=2**(-4)) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + R = self.IMAGE_TO_WATERMARK_RATIO + + watermarked = empty_like(host) + + for i in range(0, host.shape[0], R): + for j in range(0, host.shape[1], R): + watermarked[i:i + R, j:j + R] = dctn(host[i:i + R, j:j + R]) + + watermarked[i, j + 1] += self.sf * watermark[i // R, j // R] + watermarked[i, j + 2] += self.sf * watermark[i // R, j // R] + + self.svd_watermarker = chandraWatermarker(self.sf) + watermarked[::R, ::R] = self.svd_watermarker.add_watermark( + watermarked[::R, ::R], watermark) + + for i in range(0, host.shape[0], R): + for j in range(0, host.shape[1], R): + watermarked[i:i + R, j:j + R] = idctn(watermarked[i:i + R, + j:j + R]) + + return watermarked + + def check_watermark(self, watermarked_image): + pass + + def extract_watermark(self, watermarked_image): + R = self.IMAGE_TO_WATERMARK_RATIO + + watermarked_ = empty( + shape=( + watermarked_image.shape[0] // R, + watermarked_image.shape[1] // R, + ), + dtype=watermarked_image.dtype, + ) + + for i in range(0, watermarked_image.shape[0], R): + for j in range(0, watermarked_image.shape[1], R): + watermarked_[i // R, + j // R] = dctn(watermarked_image[i:i + R, + j:j + R])[0, 0] + + return self.svd_watermarker.extract_watermark(watermarked_) diff --git a/Yavuz2007.py b/Yavuz2007.py new file mode 100644 index 0000000..0797b10 --- /dev/null +++ b/Yavuz2007.py @@ -0,0 +1,76 @@ +""" +Yavuz, E., & Telatar, Z. (2007). +Improved SVD-DWT based digital image watermarking against watermark ambiguity. +Proceedings of the 2007 ACM Symposium on Applied Computing - SAC ’07, 1051. +https://doi.org/10.1145/1244002.1244232 +""" + +from numpy import isclose, log2, sqrt +from numpy.core.multiarray import empty_like +from numpy.core.numeric import zeros_like +from numpy.linalg import svd +from pywt import wavedec2, waverec2 + +from Chandra2002 import Watermarker as chandraWatermarker + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 2**3 + + def __init__(self, scale_factor=.1) -> None: + self.sf = scale_factor + + def add_watermark(self, host, watermark): + self.level = int(log2(host.shape[0]//watermark.shape[0])) + + self.svd_watermarker_1 = chandraWatermarker(self.sf) + self.svd_watermarker_2 = chandraWatermarker(self.sf*.15) + + # 3rd level DWT is applied to the cover image and LL, HL, LH and HH subbands are obtained + LL, (HL, self.LH, self.HH), *a = wavedec2(host, + wavelet='haar', + level=self.level) + + LL3_ = self.svd_watermarker_1.add_watermark(LL, watermark) + HL3_ = self.svd_watermarker_2.add_watermark(HL, watermark) + + # Components of U matrix of the watermark are embedded into LH and HH subbands + # self.U_w, s_w, self.Vh_w = svd(watermark, full_matrices=False) + + LH3_ = self.LH + self.svd_watermarker_1.U_w * self.sf + HH3_ = self.HH + self.svd_watermarker_1.U_w * self.sf + + return waverec2( + (LL3_, (HL3_, LH3_, HH3_), *a), + wavelet='haar', + ) + + def extract_watermark(self, watermarked_image): + LL, (HL, LH, HH), *a = wavedec2(watermarked_image, + wavelet='haar', + level=self.level) + + U_w_ = (LH + HH - self.LH - self.HH) / (2*self.sf) + ncc = (U_w_ * self.svd_watermarker_1.U_w).sum() / \ + (sqrt((U_w_**2).sum()) * sqrt((self.svd_watermarker_1.U_w**2).sum())) + + if ncc > .2: + return self.svd_watermarker_1.extract_watermark(LL) + return self.svd_watermarker_2.extract_watermark(HL) + else: + return zeros_like(LL) + + def extract_watermarks(self, watermarked_image): + LL, (HL, LH, HH), *a = wavedec2(watermarked_image, + wavelet='haar', + level=self.level) + + U_w_ = (LH + HH - self.LH - self.HH) / (2*self.sf) + ncc = (U_w_ * self.svd_watermarker_1.U_w).sum() / \ + (sqrt((U_w_**2).sum()) * sqrt((self.svd_watermarker_1.U_w**2).sum())) + + if ncc > .2: + return (self.svd_watermarker_1.extract_watermark(LL), + self.svd_watermarker_2.extract_watermark(HL)) + else: + return zeros_like(LL), zeros_like(LL) diff --git a/Zhu2022.py b/Zhu2022.py new file mode 100644 index 0000000..02e3c90 --- /dev/null +++ b/Zhu2022.py @@ -0,0 +1,28 @@ +""" +Zhu, T., Qu, W., & Cao, W. (2022). +An optimized image watermarking algorithm based on SVD and IWT. +The Journal of Supercomputing, 78(1), 222–237. +https://doi.org/10.1007/s11227-021-03886-2 +""" + +from pywt import dwt2, idwt2 + +from Sun2002 import Watermarker as SunWatermarker + + +class Watermarker: + IMAGE_TO_WATERMARK_RATIO = 2 + + def __init__(self, scale_factor=2**(-4)) -> None: + self.sf = scale_factor + self.svd_watermarker = SunWatermarker(self.sf) + self.IMAGE_TO_WATERMARK_RATIO *= self.svd_watermarker.IMAGE_TO_WATERMARK_RATIO + + def add_watermark(self, host, watermark): + LL, H = dwt2(host, wavelet="haar") + LL_ = self.svd_watermarker.add_watermark(LL, watermark) + return idwt2((LL_, H), wavelet="haar") + + def extract_watermark(self, watermarked_image): + LL_, _ = dwt2(watermarked_image, wavelet="haar") + return self.svd_watermarker.extract_watermark(LL_) diff --git a/_test_with_random_matrix.py b/_test_with_random_matrix.py new file mode 100644 index 0000000..5e1d977 --- /dev/null +++ b/_test_with_random_matrix.py @@ -0,0 +1,16 @@ +from numpy import asarray, isclose, random + +from Ganic2004 import Watermarker + +watermarker = Watermarker(.01) +host = random.random((500, 600)) +watermark = random.random(asarray(host.shape) // watermarker.IMAGE_TO_WATERMARK_RATIO) +watermark[watermark >= .5] = 1 +watermark[watermark < .5] = 0 +watermarked = watermarker.add_watermark(host, watermark) +watermark_ = watermarker.extract_watermark(watermarked) +c = isclose(watermark, watermark_) +print("Watermarking Test result:") +print("method:".ljust(10),Watermarker.__module__) +print("max err:".ljust(10),abs(watermark - watermark_).max()) +print("isclose:".ljust(10),f"{watermark[c].size / watermark.size:>.2%}") diff --git a/_test_with_sample_image/_test.py b/_test_with_sample_image/_test.py new file mode 100644 index 0000000..bedc807 --- /dev/null +++ b/_test_with_sample_image/_test.py @@ -0,0 +1,478 @@ +import io +import os +from datetime import datetime +from importlib import import_module +from pathlib import Path + +# import imagecodecs +# import imageio +# import napari +# from cv2 import GaussianBlur +from fpdf import FPDF, enums +from numpy import (asarray, ascontiguousarray, clip, eye, indices, inf, log10, + seterr, sqrt) +from numpy.linalg import matrix_power +from numpy.random import RandomState +from numpy.typing import ArrayLike +from PIL import Image, ImageFilter +from scipy.ndimage import gaussian_filter +from simplejpeg import decode_jpeg, encode_jpeg +from skimage import color, exposure, transform +from skimage.metrics import structural_similarity +from tifffile import tifffile + +# seterr(all="raise") +k, p, q = 5, 12, 13 +p = q = 1 +k = 0 + +out = Path("test results") + + +def arnold_transform1(img: ArrayLike, k: int) -> ArrayLike: + T = eye(2, dtype=int) # 2x2 Birim matris + + for _ in range(2 * k): + T[:] = [[T[1, 0], T[1, 1]], [T[1, 1], T[1, 0] + T[1, 1]]] + + T_inv = asarray([[T[1, 1], -T[0, 1]], [-T[1, 0], T[0, 0]]]) + + return coordinate_transform(img, T), T_inv + + +def arnold_transform(image, k, p=1, q=1): + T = matrix_power([[1, p], [q, p * q + 1]], k) + + T_inv = asarray([ + [T[1, 1], -T[0, 1]], + [-T[1, 0], T[0, 0]], + ]) + + return coordinate_transform(image, T), T_inv + + +def coordinate_transform(image, T): + i = indices(image.shape) + i = (T @ i.reshape(2, -1)).reshape(i.shape).astype(int) + + return image[i[0] % image.shape[0], i[1] % image.shape[1]] + + +def report(friendyname, name, bitdepth=8, format="png"): + def decorator(attack): + def wrapper(self, *args, **kwargs): + watermarked__ = self._pre_process( + watermarked_ := attack( + self, + watermarked := self._post_process( + self.watermarked, + bitdepth=bitdepth, + ), + *args, + **kwargs, + ), + bitdepth=bitdepth, + ) + + psnr = self.PSNR(self.host, watermarked__) + ssim = self.SSIM(self.host, watermarked__) + + watermarked___ = self.truncate(watermarked_, bitdepth=bitdepth) + + self.pdf2.add_page(format=(watermarked___.shape[1] * 2 + 4, 12)) + self.pdf2.cell(0, txt=name.format(*args, **kwargs), align="C") + + self.pdf2.add_page(format=( + watermarked___.shape[1] * 2 + 4, + watermarked___.shape[0] + 12 * 2, + ), ) + + self.pdf2.start_section( + f"{friendyname.format(*args, **kwargs)} PSNR:{psnr:3.1f} dB") + + self.pdf2.image( + Image.fromarray(watermarked___), + x=0, + # y=(self.pdf2.h-12-watermarked_.shape[0])/2, + y=0, + alt_text=friendyname.format(*args, **kwargs), + # type=format, + ) + self.pdf2.y = self.pdf2.h - 12 + self.pdf2.cell(self.pdf2.w // 2, + txt=f"PSNR:{psnr:.1f}dB", + align="C") + + if hasattr(self.watermarker, "extract_watermarks"): + ncc_str = "" + ssim_str = "" + for i, watermark_extracted in enumerate( + self._extract_watermarks(watermarked__)): + ncc = self.NCC(self.watermark, watermark_extracted) + ssim = self.SSIM(self.watermark, watermark_extracted) + + self.pdf2.start_section(f"NCC:{ncc:6.1%} SSIM:{ssim:6.1%}", + level=1) + + watermark_extracted_ = self.truncate( + self._post_process(watermark_extracted)) + + a = self.pdf2.w // 4 - watermark_extracted_.shape[0] + b = (self.pdf2.h - 12*2) // 2 - \ + watermark_extracted_.shape[1] + + padx = a // 2 + (a + watermark_extracted_.shape[1] + + 2) * (i % 2) + 2 + pady = b // 2 + (b + watermark_extracted_.shape[0] + + 12) * (i // 2) + + self.pdf2.image( + Image.fromarray(watermark_extracted_), + x=self.pdf2.w // 2 + padx, + y=pady, + ) + self.pdf2.y = pady + watermark_extracted_.shape[1] + self.pdf2.x = padx + watermarked.shape[0] + self.pdf2.cell(watermark_extracted_.shape[1], + txt=f"NCC:{ncc:.1%}", + align="C") + + ncc_str += ('&' if i % 2 else r'\\') + f" {100*ssim:.1f}\%" + ssim_str += ('&' if i % 2 else r'\\') + f" {100*ncc:.1f}\%" + else: + watermark_extracted_ = self.truncate( + self._post_process(watermark_extracted := + self._extract_watermark(watermarked__))) + + if not watermark_extracted.imag.any(): + self.watermark = self.watermark.real + + ncc = self.NCC(self.watermark, watermark_extracted) + ssim = self.SSIM(self.watermark, watermark_extracted) + self.pdf2.start_section(f"NCC:{ncc:6.1%} SSIM:{ssim:6.1%}", + level=1) + + padx = (self.pdf2.w // 2 - + watermark_extracted.shape[1]) // 2 + 4 + pady = (self.pdf2.h - 12 * 2 - + watermark_extracted.shape[0]) // 2 + + self.pdf2.image( + Image.fromarray(watermark_extracted_), + x=self.pdf2.w // 2 + padx, + y=pady, + ) + self.pdf2.cell(self.pdf2.w // 2, + txt=f"NCC:{ncc:.1%}", + align="C") + + return wrapper + + return decorator + + +class Test(): + SF = 1 + + # sf = 2**-5 + + def truncate(self, watermarked, bitdepth=8): + return (clip(watermarked, 0, 1) * + (2**bitdepth - 1)).astype(f"uint{bitdepth}") + + def _pre_process(self, image, bitdepth=None): + if len(image.shape) == 3: + return color.rgb2gray(image) + return image + + def _post_process(self, image, bitdepth=None): + return image + + def imread(self, path, size=None): + image = asarray(Image.open(path)) + # image = imageio.imread(path) + image = image / (2**(8 * image.dtype.itemsize) - 1) + if size is not None: + image = transform.resize(image, size) + return self._pre_process(image) + + # def imwrite(self, path, image, *args, bitdepth=8, **kvargs): + # # https://github.com/numpy/numpy/issues/15630 + # # np.clip with complex input is untested and has odd behavior #15630 + # image2 = (clip(self._post_process(image, bitdepth=bitdepth), 0, 1) * + # (2**bitdepth - 1)).astype(f"uint{bitdepth}") + # # working_path / + # return imageio.imsave(path, image2, *args, **kvargs) + + def MSE(self, image1, image2): + return ((self._post_process(image1) - + self._post_process(image2))**2).mean() + + def PSNR(self, image1, image2): + return 10 * log10(1 / self.MSE(image1, image2)) + + def NCC(self, image1, image2): + image1 = self._post_process(image1) + image2 = self._post_process(image2) + return (image1 * image2).sum() / (sqrt((image1**2).sum()) * sqrt( + (image2**2).sum())) + + def SSIM(self, image1, image2): + image1 = self._post_process(image1) + image2 = self._post_process(image2) + return structural_similarity(image1, image2, channel_axis=-1) + + ################################################### + + @report("PNG", "png") + def test_in_memory(self, watermarked): + return watermarked + + @report("PNG ({bitdepth} bit)", "png{bitdepth}") + def test_bitdepth(self, watermarked, bitdepth=8): + io_buf = io.BytesIO() + tifffile.imwrite(io_buf, self.truncate(watermarked, bitdepth=bitdepth)) + io_buf.seek(0) + attacked = asarray(tifffile.imread(io_buf)) + return attacked / (2**(8 * attacked.dtype.itemsize) - 1) + + @report("{compression} Sıkıştırma", "{compression}") + def test_lossy_compression(self, watermarked, compression, *args): + io_buf = io.BytesIO() + # tifffile.imwrite(io_buf, + # self.truncate(watermarked), + # compression=compression) + # io_buf.seek(0) + # attacked = asarray(tifffile.imread(io_buf)) + Image.fromarray(watermarked).save( + io_buf, + format=compression, + ) + attacked = asarray(Image.open(io_buf)) + return attacked / (2**(8 * attacked.dtype.itemsize) - 1) + + @report("JPEG (Q={q})", "jpg{q}", format="jpeg") + def test_jpeg_compression(self, watermarked, q=95): + # io_buf = io.BytesIO() + if len(watermarked.shape) == 2: + watermarked = watermarked.reshape(*watermarked.shape, 1) + colorspace = 'GRAY' if watermarked.shape[2] == 1 else 'RGB' + watermarked_ = decode_jpeg( + j := encode_jpeg(a := + ascontiguousarray(self.truncate(watermarked)), + colorspace=colorspace, + quality=q), ) + # tifffile.imwrite(io_buf, + # self.truncate(watermarked), + # compression="jpeg") + # io_buf.seek(0) + # attacked = asarray(tifffile.imread(io_buf)) + # self.watermarked_pil_image = Image.fromarray( + # a := self.truncate(self.watermarked, bitdepth=8)) + + # self.watermarked_pil_image.save( + # io_buf, + # format="jpeg", + # quality=q, + # ) + # attacked = asarray(Image.open(io_buf)) + return watermarked_ / (2**(8 * watermarked_.dtype.itemsize) - 1) + + @report("FPP0 Testi", "ffp0") + def test_false_pasitive_0(self, watermarked): + return self._post_process(self.host) + + @report("FPP1 Testi", "ffp1") + def test_false_pasitive_1(self, watermarked): + watermarked = watermarked.copy() + self._add_watermark(watermark=self.watermark2) + return watermarked + + @report("Histogram Eşitleme", "hist") + def histogram_equalize(self, watermarked): + return exposure.equalize_hist(watermarked) + + @report("{angle} Döndürme", "rotate{angle}") + def test_rotation(self, watermarked, angle=1): + # return asarray(self.watermarked_pil_image.rotate(angle)) + return transform.rotate(watermarked, angle) + + @report("Kırpma ({r})", "crop{r}") + def test_crop(self, watermarked, r=16): + watermarked_ = watermarked.copy() + watermarked_[:watermarked_.shape[0] // r] = 0 + watermarked_[-watermarked_.shape[0] // r:] = 0 + watermarked_[:, :watermarked_.shape[1] // r] = 0 + watermarked_[:, -watermarked_.shape[1] // r:] = 0 + return watermarked_ + + @report("Gaussian Blur ({sigma})", "blur{sigma}") + def test_gaussian_blur(self, watermarked, sigma=1): + # return asarray( + # self.watermarked_pil_image.filter( + # ImageFilter.GaussianBlur(radius=radius))) + return gaussian_filter(watermarked, sigma=sigma) + # return GaussianBlur(watermarked, ksize=ksize) + + @report("Gaussian Noise ({weight:.0%})", "noise{weight:.0f}") + def test_gaussian_noise(self, watermarked, weight=.3): + random = RandomState(11052022) + noise = random.random(size=watermarked.shape) + return watermarked + noise * weight + + ################################################### + + def _add_watermark(self, watermark=None): + if watermark is None: + watermark = self.watermark + + k, p, q = self.at + if p == q == 1: + watermark, self.KEY = arnold_transform1(watermark, k) + else: + watermark, self.KEY = arnold_transform(watermark, k, p, q) + + self.watermarked = self.watermarker.add_watermark(self.host, watermark) + + def _extract_watermark(self, watermarked): + watermark = self.watermarker.extract_watermark(watermarked) + return coordinate_transform(watermark, self.KEY) + + def _extract_watermarks(self, watermarked): + return (coordinate_transform(image, self.KEY) + for image in self.watermarker.extract_watermarks(watermarked)) + + ################################################### + + def __init__(self, + watermarker, + host_path, + watermark_path, + watermark2_path, + working_path, + k=0, + p=1, + q=1): + self.at = k, p, q + + working_path.parent.mkdir(parents=True, exist_ok=True) + self.working_path = working_path + + self.watermarker = watermarker + + self.host = self.imread(host_path) + + self.watermark = self.imread( + watermark_path, + size=( + self.host.shape[0] // watermarker.IMAGE_TO_WATERMARK_RATIO, + self.host.shape[1] // watermarker.IMAGE_TO_WATERMARK_RATIO, + )) + + self.watermark2 = self.imread( + watermark2_path, + size=( + self.host.shape[0] // watermarker.IMAGE_TO_WATERMARK_RATIO, + self.host.shape[1] // watermarker.IMAGE_TO_WATERMARK_RATIO, + )) + + def tests(self, module): + self._add_watermark() + + self.pdf2 = FPDF( + unit="pt", + # format=( + # self.host.shape[1] * 2 + 4, + # self.host.shape[0] + 12 * 2, + # ), + ) + self.pdf2.set_display_mode( + zoom="real", + layout="single", + ) + self.pdf2.set_creation_date(datetime.fromisoformat("2022-06-27")) + self.pdf2.set_compression(compress=True) + self.pdf2.set_author("Naci ER") + self.pdf2.set_subject("PhD thesis attachment") + self.pdf2.set_title(module) + self.pdf2.set_font("times") + self.pdf2.set_font_size(12) + self.pdf2.set_margin(0) + self.pdf2.set_auto_page_break(False) + + self.test_in_memory() + + self.test_bitdepth(bitdepth=16) + self.test_bitdepth(bitdepth=8) + + self.test_jpeg_compression(q=90) + self.test_jpeg_compression(q=60) + self.test_jpeg_compression(q=30) + + # self.test_lossy_compression(compression="jp2") + # self.test_lossy_compression(compression="webp") + + self.histogram_equalize() + + self.test_rotation(angle=1) + self.test_rotation(angle=30) + self.test_rotation(angle=90) + + self.test_crop(r=16) + + self.test_gaussian_blur(sigma=2) + self.test_gaussian_noise(weight=.1) + + self.test_false_pasitive_0() + self.test_false_pasitive_1() + + self.pdf2.output(self.working_path.as_posix()) + + +def main(Test, + host="test images/host_image.png", + watermark1="test images/watermark.png", + watermark2="test images/watermark2.png", + k=0, + p=1, + q=1): + + import sys + cwd = Path(__file__).parent.parent + sys.path.append(str(cwd)) + out.mkdir(parents=True, exist_ok=True) + + for module in cwd.iterdir(): + if module.name.startswith(".") or module.name.startswith("_") or module.is_dir(): + continue + module = module.name.removesuffix(".py") + print(module) + + watermarker = import_module(module).Watermarker() + + if hasattr(Test, "sf"): + watermarker.sf = Test.sf + else: + watermarker.sf *= Test.SF + + t = Test( + watermarker, + host, + watermark1, + watermark2, + out / + f"{module}/{module}.w={watermarker.sf:g}{f'.k={k}' if k else ''}{'.p={p}.q={q}' if k and p!=1 else ''}.pdf", + k=k, + p=p, + q=q) + + if not t.working_path.is_file(): + try: + t.tests(module) + except Exception as err: + sys.stderr.write(f"{str(err)}") + + +if __name__ == "__main__": + print(__file__) + main(Test, k=k, p=p, q=q) diff --git a/_test_with_sample_image/test images/host_image.png b/_test_with_sample_image/test images/host_image.png new file mode 100644 index 0000000..5eef273 Binary files /dev/null and b/_test_with_sample_image/test images/host_image.png differ diff --git a/_test_with_sample_image/test images/host_image_.png b/_test_with_sample_image/test images/host_image_.png new file mode 100644 index 0000000..d53c74f Binary files /dev/null and b/_test_with_sample_image/test images/host_image_.png differ diff --git a/_test_with_sample_image/test images/watermark.png b/_test_with_sample_image/test images/watermark.png new file mode 100644 index 0000000..83298cd Binary files /dev/null and b/_test_with_sample_image/test images/watermark.png differ diff --git a/_test_with_sample_image/test images/watermark2.png b/_test_with_sample_image/test images/watermark2.png new file mode 100644 index 0000000..4c04989 Binary files /dev/null and b/_test_with_sample_image/test images/watermark2.png differ diff --git a/_test_with_sample_image/test images/watermark2_.png b/_test_with_sample_image/test images/watermark2_.png new file mode 100644 index 0000000..ada8c79 Binary files /dev/null and b/_test_with_sample_image/test images/watermark2_.png differ diff --git a/_test_with_sample_image/test images/watermark_.png b/_test_with_sample_image/test images/watermark_.png new file mode 100644 index 0000000..4a93dc7 Binary files /dev/null and b/_test_with_sample_image/test images/watermark_.png differ diff --git a/_test_with_sample_image/test results/Ambadekar2019/Ambadekar2019.w=0.015625.k=5.pdf b/_test_with_sample_image/test results/Ambadekar2019/Ambadekar2019.w=0.015625.k=5.pdf new file mode 100644 index 0000000..f05638f Binary files /dev/null and b/_test_with_sample_image/test results/Ambadekar2019/Ambadekar2019.w=0.015625.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Ambadekar2019/Ambadekar2019.w=0.015625.pdf b/_test_with_sample_image/test results/Ambadekar2019/Ambadekar2019.w=0.015625.pdf new file mode 100644 index 0000000..934fd3e Binary files /dev/null and b/_test_with_sample_image/test results/Ambadekar2019/Ambadekar2019.w=0.015625.pdf differ diff --git a/_test_with_sample_image/test results/Chandra2002/Chandra2002.w=0.0625.k=5.pdf b/_test_with_sample_image/test results/Chandra2002/Chandra2002.w=0.0625.k=5.pdf new file mode 100644 index 0000000..86f082c Binary files /dev/null and b/_test_with_sample_image/test results/Chandra2002/Chandra2002.w=0.0625.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Chandra2002/Chandra2002.w=0.0625.pdf b/_test_with_sample_image/test results/Chandra2002/Chandra2002.w=0.0625.pdf new file mode 100644 index 0000000..3f64780 Binary files /dev/null and b/_test_with_sample_image/test results/Chandra2002/Chandra2002.w=0.0625.pdf differ diff --git a/_test_with_sample_image/test results/Chang2005/Chang2005.w=0.012.k=5.pdf b/_test_with_sample_image/test results/Chang2005/Chang2005.w=0.012.k=5.pdf new file mode 100644 index 0000000..34eeea7 Binary files /dev/null and b/_test_with_sample_image/test results/Chang2005/Chang2005.w=0.012.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Chang2005/Chang2005.w=0.012.pdf b/_test_with_sample_image/test results/Chang2005/Chang2005.w=0.012.pdf new file mode 100644 index 0000000..6eaff1e Binary files /dev/null and b/_test_with_sample_image/test results/Chang2005/Chang2005.w=0.012.pdf differ diff --git a/_test_with_sample_image/test results/Ganic2004/Ganic2004.w=0.05.k=5.pdf b/_test_with_sample_image/test results/Ganic2004/Ganic2004.w=0.05.k=5.pdf new file mode 100644 index 0000000..361b2a7 Binary files /dev/null and b/_test_with_sample_image/test results/Ganic2004/Ganic2004.w=0.05.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Ganic2004/Ganic2004.w=0.05.pdf b/_test_with_sample_image/test results/Ganic2004/Ganic2004.w=0.05.pdf new file mode 100644 index 0000000..a6757b1 Binary files /dev/null and b/_test_with_sample_image/test results/Ganic2004/Ganic2004.w=0.05.pdf differ diff --git a/_test_with_sample_image/test results/Guo2014/Guo2014.w=0.01.k=5.pdf b/_test_with_sample_image/test results/Guo2014/Guo2014.w=0.01.k=5.pdf new file mode 100644 index 0000000..faf3566 Binary files /dev/null and b/_test_with_sample_image/test results/Guo2014/Guo2014.w=0.01.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Guo2014/Guo2014.w=0.01.pdf b/_test_with_sample_image/test results/Guo2014/Guo2014.w=0.01.pdf new file mode 100644 index 0000000..a7941b3 Binary files /dev/null and b/_test_with_sample_image/test results/Guo2014/Guo2014.w=0.01.pdf differ diff --git a/_test_with_sample_image/test results/Gupta2012/Gupta2012.w=0.02.k=5.pdf b/_test_with_sample_image/test results/Gupta2012/Gupta2012.w=0.02.k=5.pdf new file mode 100644 index 0000000..1f03b02 Binary files /dev/null and b/_test_with_sample_image/test results/Gupta2012/Gupta2012.w=0.02.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Gupta2012/Gupta2012.w=0.02.pdf b/_test_with_sample_image/test results/Gupta2012/Gupta2012.w=0.02.pdf new file mode 100644 index 0000000..8ca29ca Binary files /dev/null and b/_test_with_sample_image/test results/Gupta2012/Gupta2012.w=0.02.pdf differ diff --git a/_test_with_sample_image/test results/Jain2008/Jain2008.w=0.02.k=5.pdf b/_test_with_sample_image/test results/Jain2008/Jain2008.w=0.02.k=5.pdf new file mode 100644 index 0000000..5bf5074 Binary files /dev/null and b/_test_with_sample_image/test results/Jain2008/Jain2008.w=0.02.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Jain2008/Jain2008.w=0.02.pdf b/_test_with_sample_image/test results/Jain2008/Jain2008.w=0.02.pdf new file mode 100644 index 0000000..860793e Binary files /dev/null and b/_test_with_sample_image/test results/Jain2008/Jain2008.w=0.02.pdf differ diff --git a/_test_with_sample_image/test results/Jane2014/Jane2014.w=0.05.k=5.pdf b/_test_with_sample_image/test results/Jane2014/Jane2014.w=0.05.k=5.pdf new file mode 100644 index 0000000..96e7e49 Binary files /dev/null and b/_test_with_sample_image/test results/Jane2014/Jane2014.w=0.05.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Jane2014/Jane2014.w=0.05.pdf b/_test_with_sample_image/test results/Jane2014/Jane2014.w=0.05.pdf new file mode 100644 index 0000000..388040f Binary files /dev/null and b/_test_with_sample_image/test results/Jane2014/Jane2014.w=0.05.pdf differ diff --git a/_test_with_sample_image/test results/Lai2010/Lai2010.w=0.01.k=5.pdf b/_test_with_sample_image/test results/Lai2010/Lai2010.w=0.01.k=5.pdf new file mode 100644 index 0000000..970f77b Binary files /dev/null and b/_test_with_sample_image/test results/Lai2010/Lai2010.w=0.01.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Lai2010/Lai2010.w=0.01.pdf b/_test_with_sample_image/test results/Lai2010/Lai2010.w=0.01.pdf new file mode 100644 index 0000000..f788b97 Binary files /dev/null and b/_test_with_sample_image/test results/Lai2010/Lai2010.w=0.01.pdf differ diff --git a/_test_with_sample_image/test results/Liu2002/Liu2002.w=0.1.k=5.pdf b/_test_with_sample_image/test results/Liu2002/Liu2002.w=0.1.k=5.pdf new file mode 100644 index 0000000..61da048 Binary files /dev/null and b/_test_with_sample_image/test results/Liu2002/Liu2002.w=0.1.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Liu2002/Liu2002.w=0.1.pdf b/_test_with_sample_image/test results/Liu2002/Liu2002.w=0.1.pdf new file mode 100644 index 0000000..75d17b7 Binary files /dev/null and b/_test_with_sample_image/test results/Liu2002/Liu2002.w=0.1.pdf differ diff --git a/_test_with_sample_image/test results/Liu2019/Liu2019.w=0.0625.k=5.pdf b/_test_with_sample_image/test results/Liu2019/Liu2019.w=0.0625.k=5.pdf new file mode 100644 index 0000000..ada8998 Binary files /dev/null and b/_test_with_sample_image/test results/Liu2019/Liu2019.w=0.0625.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Liu2019/Liu2019.w=0.0625.pdf b/_test_with_sample_image/test results/Liu2019/Liu2019.w=0.0625.pdf new file mode 100644 index 0000000..90a96c7 Binary files /dev/null and b/_test_with_sample_image/test results/Liu2019/Liu2019.w=0.0625.pdf differ diff --git a/_test_with_sample_image/test results/Mohammad2008/Mohammad2008.w=0.02.k=5.pdf b/_test_with_sample_image/test results/Mohammad2008/Mohammad2008.w=0.02.k=5.pdf new file mode 100644 index 0000000..820f517 Binary files /dev/null and b/_test_with_sample_image/test results/Mohammad2008/Mohammad2008.w=0.02.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Mohammad2008/Mohammad2008.w=0.02.pdf b/_test_with_sample_image/test results/Mohammad2008/Mohammad2008.w=0.02.pdf new file mode 100644 index 0000000..f6913d6 Binary files /dev/null and b/_test_with_sample_image/test results/Mohammad2008/Mohammad2008.w=0.02.pdf differ diff --git a/_test_with_sample_image/test results/Niu2016/Niu2016.w=0.0625.k=5.pdf b/_test_with_sample_image/test results/Niu2016/Niu2016.w=0.0625.k=5.pdf new file mode 100644 index 0000000..f6d4f18 Binary files /dev/null and b/_test_with_sample_image/test results/Niu2016/Niu2016.w=0.0625.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Niu2016/Niu2016.w=0.0625.pdf b/_test_with_sample_image/test results/Niu2016/Niu2016.w=0.0625.pdf new file mode 100644 index 0000000..f1b2c38 Binary files /dev/null and b/_test_with_sample_image/test results/Niu2016/Niu2016.w=0.0625.pdf differ diff --git a/_test_with_sample_image/test results/Run2012/Run2012.w=0.05.k=5.pdf b/_test_with_sample_image/test results/Run2012/Run2012.w=0.05.k=5.pdf new file mode 100644 index 0000000..0cd4aeb Binary files /dev/null and b/_test_with_sample_image/test results/Run2012/Run2012.w=0.05.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Run2012/Run2012.w=0.05.pdf b/_test_with_sample_image/test results/Run2012/Run2012.w=0.05.pdf new file mode 100644 index 0000000..d180fa0 Binary files /dev/null and b/_test_with_sample_image/test results/Run2012/Run2012.w=0.05.pdf differ diff --git a/_test_with_sample_image/test results/Sun2002/Sun2002.w=0.0625.k=5.pdf b/_test_with_sample_image/test results/Sun2002/Sun2002.w=0.0625.k=5.pdf new file mode 100644 index 0000000..8654623 Binary files /dev/null and b/_test_with_sample_image/test results/Sun2002/Sun2002.w=0.0625.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Sun2002/Sun2002.w=0.0625.pdf b/_test_with_sample_image/test results/Sun2002/Sun2002.w=0.0625.pdf new file mode 100644 index 0000000..aa81d42 Binary files /dev/null and b/_test_with_sample_image/test results/Sun2002/Sun2002.w=0.0625.pdf differ diff --git a/_test_with_sample_image/test results/Xing2007/Xing2007.w=0.0625.k=5.pdf b/_test_with_sample_image/test results/Xing2007/Xing2007.w=0.0625.k=5.pdf new file mode 100644 index 0000000..212f58e Binary files /dev/null and b/_test_with_sample_image/test results/Xing2007/Xing2007.w=0.0625.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Xing2007/Xing2007.w=0.0625.pdf b/_test_with_sample_image/test results/Xing2007/Xing2007.w=0.0625.pdf new file mode 100644 index 0000000..a344590 Binary files /dev/null and b/_test_with_sample_image/test results/Xing2007/Xing2007.w=0.0625.pdf differ diff --git a/_test_with_sample_image/test results/Yavuz2006/Yavuz2006.w=0.0625.k=5.pdf b/_test_with_sample_image/test results/Yavuz2006/Yavuz2006.w=0.0625.k=5.pdf new file mode 100644 index 0000000..7e95131 Binary files /dev/null and b/_test_with_sample_image/test results/Yavuz2006/Yavuz2006.w=0.0625.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Yavuz2006/Yavuz2006.w=0.0625.pdf b/_test_with_sample_image/test results/Yavuz2006/Yavuz2006.w=0.0625.pdf new file mode 100644 index 0000000..cf30eb5 Binary files /dev/null and b/_test_with_sample_image/test results/Yavuz2006/Yavuz2006.w=0.0625.pdf differ diff --git a/_test_with_sample_image/test results/Yavuz2007/Yavuz2007.w=0.1.k=5.pdf b/_test_with_sample_image/test results/Yavuz2007/Yavuz2007.w=0.1.k=5.pdf new file mode 100644 index 0000000..c7e4d43 Binary files /dev/null and b/_test_with_sample_image/test results/Yavuz2007/Yavuz2007.w=0.1.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Yavuz2007/Yavuz2007.w=0.1.pdf b/_test_with_sample_image/test results/Yavuz2007/Yavuz2007.w=0.1.pdf new file mode 100644 index 0000000..b99c8d5 Binary files /dev/null and b/_test_with_sample_image/test results/Yavuz2007/Yavuz2007.w=0.1.pdf differ diff --git a/_test_with_sample_image/test results/Zhu2022/Zhu2022.w=0.0625.k=5.pdf b/_test_with_sample_image/test results/Zhu2022/Zhu2022.w=0.0625.k=5.pdf new file mode 100644 index 0000000..ff2cb80 Binary files /dev/null and b/_test_with_sample_image/test results/Zhu2022/Zhu2022.w=0.0625.k=5.pdf differ diff --git a/_test_with_sample_image/test results/Zhu2022/Zhu2022.w=0.0625.pdf b/_test_with_sample_image/test results/Zhu2022/Zhu2022.w=0.0625.pdf new file mode 100644 index 0000000..52226de Binary files /dev/null and b/_test_with_sample_image/test results/Zhu2022/Zhu2022.w=0.0625.pdf differ diff --git a/docs/CITATION.cff b/docs/CITATION.cff new file mode 100644 index 0000000..a3a1276 --- /dev/null +++ b/docs/CITATION.cff @@ -0,0 +1,10 @@ +cff-version: 1.1.0 +message: "If you use this software, please cite it as below." +authors: + - family-names: "Er" + given-names: "Naci" + orcid: https://orcid.org/0000-0002-4218-7788 +title: "naaci/watermarking: V1.0.0" +version: V1.0.0 +date-released: 2023-01-29 +DOI: 10.5281/zenodo.7582082 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..1b7e8c3 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,6 @@ +[![DOI](https://zenodo.org/badge/444999271.svg)](https://zenodo.org/badge/latestdoi/444999271) + +# watermarking +Implementations of some digital image watermarking schemes for grayscale images proposed by various authors. + +Each module defines a class named `watermarking` with two methods namely `add_watermark` and `extract_watermark`. Some modules `watermarking` class has additional method `extract_watermarks`. diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..c741881 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..64869e2 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,37 @@ +## Welcome to GitHub Pages + +You can use the [editor on GitHub](https://github.com/naaci/Digital-Image-Watermarking/edit/PhD/docs/index.md) to maintain and preview the content for your website in Markdown files. + +Whenever you commit to this repository, GitHub Pages will run [Jekyll](https://jekyllrb.com/) to rebuild the pages in your site, from the content in your Markdown files. + +### Markdown + +Markdown is a lightweight and easy-to-use syntax for styling your writing. It includes conventions for + +```markdown +Syntax highlighted code block + +# Header 1 +## Header 2 +### Header 3 + +- Bulleted +- List + +1. Numbered +2. List + +**Bold** and _Italic_ and `Code` text + +[Link](url) and ![Image](src) +``` + +For more details see [Basic writing and formatting syntax](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax). + +### Jekyll Themes + +Your Pages site will use the layout and styles from the Jekyll theme you have selected in your [repository settings](https://github.com/naaci/Digital-Image-Watermarking/settings/pages). The name of this theme is saved in the Jekyll `_config.yml` configuration file. + +### Support or Contact + +Having trouble with Pages? Check out our [documentation](https://docs.github.com/categories/github-pages-basics/) or [contact support](https://support.github.com/contact) and we’ll help you sort it out.