diff --git a/common.pyc b/common.pyc index 080bc31..27f6487 100644 Binary files a/common.pyc and b/common.pyc differ diff --git a/jpegObj/__init__.py b/jpegObj/__init__.py deleted file mode 100644 index e208c0c..0000000 --- a/jpegObj/__init__.py +++ /dev/null @@ -1,408 +0,0 @@ -## -*- coding: utf-8 -*- - -print "[pysteg.jpeg] $Id: __init__.py 2204 2011-04-05 11:43:38Z georg $" - -from mjsteg import Jsteg - -__all__ = ['Jpeg'] - -# We need standard components from :mod:`numpy`, and some auxiliary -# functions from submodules. -# -# :: - -import numpy.random as rnd -from numpy import shape -import numpy as np -import pylab as plt - -import base -from dct import bdct, ibdct - -# The colour codes are defined in the JPEG standard. We store -# them here for easy reference by name:: - -colorCode = { - "GRAYSCALE": 1, - "RGB": 2, - "YCbCr": 3, - "CMYK": 4, - "YCCK": 5 -} - -colorParam = ['Y', 'Cb', 'Cr'] -colorMap = {'Y': 0, 'Cb': 1, 'Cr': 2} - -# The JPEG class -# ============== - -class Jpeg(Jsteg): - """ - The jpeg (derived from jpegObject) allows the user to extract - a sequence of pseudo-randomly ordered jpeg coefficients for - watermarking/steganography, and reinsert them. - """ - - def __init__(self, file=None, key=None, rndkey=True, image=None, - verbosity=1, **kw): - """ - The constructor will return a new Object with data from the given file. - - The key is used to determine the order of the jpeg coefficients. - If no key is given, a random key is extracted using - random.SystemRandom(). - """ - if image != None: - raise NotImplementedError, "Compression is not yet implemented" - Jsteg.__init__(self, file, **kw) - self.verbosity = verbosity - if verbosity > 0: - print "[Jpeg.__init__] Image size %ix%i" % (self.coef_arrays[0].shape) - if key != None: - self.key = key - elif rndkey: - self.key = [base.sysrnd.getrandbits(16) for x in range(16)] - else: - self.key = None - - - def getkey(self): - """Return the key used to shuffle the coefficients.""" - return self.key - - # 1D Signal Representations - # ------------------------- - - def rawsignal(self, mask=base.acMaskBlock, channel="All"): - """ - Return a 1D array of AC coefficients. - (Most applications should use getsignal() rather than rawsignal().) - """ - R = [] - if channel == "All": - for X in self.coef_arrays: - (h, w) = X.shape - A = base.acMask(h, w, mask) - R = np.hstack([R, X[A]]) - else: - cID = self.getCompID(channel) - X = self.coef_arrays[cID] - (h, w) = X.shape - A = base.acMask(h, w, mask) - R = np.hstack([R, X[A]]) - return R - - def getsignal(self, mask=base.acMaskBlock, channel="All"): - """Return a 1D array of AC coefficients in random order.""" - R = self.rawsignal(mask, channel) - if self.key == None: - return R - else: - rnd.seed(self.key) - return R[rnd.permutation(len(R))] - - def setsignal(self, R0, mask=base.acMaskBlock, channel="All"): - """Reinserts AC coefficients from getitem in the correct positions.""" - if self.key != None: - rnd.seed(self.key) - fst = 0 - P = rnd.permutation(len(R0)) - R = np.array(R0) - R[P] = R0 - else: - R = R0 - if channel == "All": - for cID in range(3): - X = self.coef_arrays[cID] - s = X.size * 63 / 64 - (h, w) = X.shape - X[base.acMask(h, w, mask)] = R[fst:(fst + s)] - fst += s - - # Jset - blocks = self.getCoefBlocks(channel=colorParam[cID]) - xmax, ymax = self.Jgetcompdim(cID) - for y in range(ymax): - for x in range(xmax): - block = blocks[y, x] - self.Jsetblock(x, y, cID, bytearray(block.astype(np.int16))) - - else: - cID = self.getCompID(channel) - X = self.coef_arrays[cID] - s = X.size * 63 / 64 - (h, w) = X.shape - X[base.acMask(h, w, mask)] = R[fst:(fst + s)] - fst += s - - # Jset - blocks = self.getCoefBlocks(channel) - xmax, ymax = self.Jgetcompdim(cID) - for y in range(ymax): - for x in range(xmax): - block = blocks[y, x] - self.Jsetblock(x, y, cID, bytearray(block.astype(np.int16))) - - assert len(R) == fst - - - # Histogram and Image Statistics - # ------------------------------ - - def abshist(self, mask=base.acMaskBlock, T=8): - """ - Make a histogram of absolute values for a signal. - """ - A = abs(self.rawsignal(mask)).tolist() - L = len(A) - D = {} - C = 0 - for i in range(T + 1): - D[i] = A.count(i) - C += D[i] - D["high"] = L - C - D["total"] = L - return D - - def hist(self, mask=base.acMaskBlock, T=8): - """ - Make a histogram of the jpeg coefficients. - The mask is a boolean 8x8 matrix indicating the - frequencies to be included. This defaults to the - AC coefficients. - """ - A = self.rawsignal(mask).tolist() - E = [-np.inf] + [i for i in range(-T, T + 2)] + [np.inf] - return np.histogram(A, E) - - def plotHist(self, mask=base.acMaskBlock, T=8): - """ - Make a histogram of the jpeg coefficients. - The mask is a boolean 8x8 matrix indicating the - frequencies to be included. This defaults to the - AC coefficients. - """ - A = self.rawsignal(mask).tolist() - E = [i for i in range(-T, T + 2)] - plt.hist(A, E, histtype='bar') - plt.show() - - def nzcount(self, *a, **kw): - """Number of non-zero AC coefficients. - - Arguments are passed to rawsignal(), so a non-default mask could - be specified to get other coefficients than the 63 AC coefficients. - """ - R = list(self.rawsignal(*a, **kw)) - return len(R) - R.count(0) - - # Access to JPEG Image Data - # ------------------------- - - def getCompID(self, channel): - """ - Get the index of the given colour channel. - """ - # How do we adress different channels? - colourSpace = self.jpeg_color_space; - if colourSpace == colorCode["GRAYSCALE"]: - if channel == "Y": - return 0 - elif channel == None: - return 0 - else: - raise Exception, "Invalid colour space designator" - elif colourSpace == colorCode["YCbCr"]: - if channel == "Y": - return 0 - elif channel == "Cb": - return 1 - elif channel == "Cr": - return 2 - else: - raise Exception, "Invalid colour space designator" - raise NotImplementedError, "Only YCbCr and Grayscale are supported." - - def getQMatrix(self, channel): - """ - Return the quantisation matrix for the given colour channel. - """ - cID = self.getCompID(channel) - return self.quant_tables[self.comp_info[cID]["quant_tbl_no"]] - - def getCoefMatrix(self, channel="Y"): - """ - This method returns the coefficient matrix for the given - colour channel (as a matrix). - """ - cID = self.getCompID(channel) - return self.coef_arrays[cID] - - def setCoefMatrix(self, matrix, channel="Y"): - v, h = self.getCoefMatrix(channel).shape - assert matrix.shape == (v, h), "matrix is expected of size (%d,%d)" % (v, h) - - cID = self.getCompID(channel) - self.coef_arrays[cID] = matrix - - blocks = self.getCoefBlocks(channel) - xmax, ymax = self.Jgetcompdim(cID) - for y in range(ymax): - for x in range(xmax): - block = blocks[y, x] - self.Jsetblock(x, y, cID, bytearray(block.astype(np.int16))) - - def getCoefBlocks(self, channel="Y"): - """ - This method returns the coefficient matrix for the given - colour channel (as a 4-D tensor: (v,h,row,col)). - """ - if channel == "All": - return [ - np.array([np.hsplit(arr, arr.shape[1] / 8) for arr in np.vsplit(compMat, compMat.shape[0] / 8)]) - for compMat in self.coef_arrays] - - compMat = self.getCoefMatrix(channel="Y") - return np.array([np.hsplit(arr, arr.shape[1] / 8) for arr in np.vsplit(compMat, compMat.shape[0] / 8)]) - - def getCoefBlock(self, channel="Y", loc=(0, 0)): - """ - This method returns the coefficient matrix for the given - colour channel (as a 4-D tensor: (v,h,row,col)). - """ - return self.getCoefBlocks(channel)[loc] - - - def setCoefBlock(self, block, channel="Y", loc=(0, 0)): - assert block.shape == (8, 8), "block is expected of size (8,8)" - cID = self.getCompID(channel) - v, h = loc[0] * 8, loc[1] * 8 - self.coef_arrays[cID][v:v + 8, h:h + 8] = block - - self.Jsetblock(loc[1], loc[0], cID, bytearray(block.astype(np.int16))) - - def setCoefBlocks(self, blocks, channel="Y"): - assert blocks.shape[-2:] == (8, 8), "block is expected of size (8,8)" - cID = self.getCompID(channel) - - vmax, hmax = blocks.shape[:2] - for i in range(vmax): - for j in range(hmax): - v, h = i * 8, j * 8 - self.coef_arrays[cID][v:v + 8, h:h + 8] = blocks[i, j] - self.Jsetblock(j, i, cID, bytearray(blocks[i, j].astype(np.int16))) - - # Decompression - # ------------- - - def getSpatial(self, channel="Y"): - """ - This method returns one decompressed colour channel as a matrix. - The appropriate JPEG coefficient matrix is dequantised - (using the quantisation tables held by the object) and - inverse DCT transformed. - """ - X = self.getCoefMatrix(channel) - Q = self.getQMatrix(channel) - (M, N) = shape(X) - assert M % 8 == 0, "Image size not divisible by 8" - assert N % 8 == 0, "Image size not divisible by 8" - D = X * base.repmat(Q, (M / 8, N / 8)) - S = ibdct(D) - # assert max( abs(S).flatten() ) <=128, "Image colours out of range" - return (S + 128 ).astype(np.uint8) - - # Complete, general decompression is not yet implemented:: - - def getimage(self): - """ - Decompress the image and a PIL Image object. - """ - - # Probably better to use a numpy image/array. - - raise NotImplementedError, "Decompression is not yet implemented" - - # We miss the routines for upsampling and adjusting the size - - L = len(self.coef_arrays) - im = [] - for i in range(L): - C = self.coef_arrays[i] - if C != None: - Q = self.quant_tables[self.comp_info[i]["quant_tbl_no"]] - im.append(ibdct(dequantise(C, Q))) - return Image.fromarray(im) - - - # Calibration - # ----------- - - def getCalibrated(self, channel="Y", mode="all"): - """ - Return a calibrated coefficient matrix for the given channel. - Channel may be "Y", "Cb", or "Cr" for YCbCr format. - For Grayscale images, it may be None or "Y". - """ - S = self.getSpatial(channel) - (M, N) = shape(S) - assert M % 8 == 0, "Image size not divisible by 8" - assert N % 8 == 0, "Image size not divisible by 8" - if mode == "col": - S1 = S[:, 4:(N - 4)] - cShape = ( M / 8, N / 8 - 1 ) - else: - S1 = S[4:(M - 4), 4:(N - 4)] - cShape = ( (M - 1) / 8, (N - 1) / 8 ) - D = bdct(S1 - 128) - X = D / base.repmat(self.getQMatrix(channel), cShape) - return np.round(X) - - def calibrate(self, *a, **kw): - assert len(self.coef_arrays) == 1 - self.coef_arrays[0] = self.getCalibrated(*a, **kw) - - def getCalSpatial(self, channel="Y"): - """ - Return the decompressed, calibrated, grayscale image. - A different colour channel can be selected with the channel - argument. - """ - - # We calibrate the image, obtaining a JPEG matrix. - - C = self.getCalibrated(channel) - - # The rest is straight forward JPEG decompression. - - (M, N) = shape(C) - cShape = (M / 8, N / 8) - D = C * base.repmat(self.getQMatrix(channel), cShape) - S = np.round(ibdct(D) + 128) - return S.astype(np.uint8) - - -def diffblock(c1, c2): - diff = False - if np.array_equal(c1, c2): - print("blocks match") - else: - print("blocks not match") - diff = True - - return diff - - -def diffblocks(a, b): - diff = False - cnt = 0 - for comp in range(a.image_components): - xmax, ymax = a.Jgetcompdim(comp) - for y in range(ymax): - for x in range(xmax): - if a.Jgetblock(x, y, comp) != b.Jgetblock(x, y, comp): - print("blocks({},{}) in component {} not match".format(y, x, comp)) - diff = True - cnt += 1 - return diff, cnt - diff --git a/jpegObj/__init__.pyc b/jpegObj/__init__.pyc deleted file mode 100644 index 3b4cf0c..0000000 Binary files a/jpegObj/__init__.pyc and /dev/null differ diff --git a/jpegObj/base.py b/jpegObj/base.py deleted file mode 100644 index 728a59b..0000000 --- a/jpegObj/base.py +++ /dev/null @@ -1,38 +0,0 @@ -## -*- coding: utf-8 -*- - -from numpy import array, vstack, hstack, kron, ones -from random import SystemRandom - -sysrnd = SystemRandom() - - -def uMask(u=7): - return array([[(x + y < u) & (x + y > 0) for x in range(8)] for y in range(8)]) - - -acMaskBlock = array( - [[False] + [True for x in range(7)]] + - [[True for x in range(8)] for y in range(7)] -) - - -def acMask(h1, w1, mask=acMaskBlock): - """Return a mask of the given size for the AC coefficients of a - JPEG coefficient matrix.""" - (h, w) = (h1 / 8, w1 / 8) - A = vstack([mask for x in range(h)]) - A = hstack([A for x in range(w)]) - return A - - -def repmat(M, rep): return kron(ones(rep), M) - - -def getfreq(A, i, j): - """ - Return a submatrix of a JPEG matrix A including only frequency (i,j) - from each block. - """ - (M, N) = A.shape - return A[xrange(i, M, 8), :][:, xrange(j, N, 8)] - diff --git a/jpegObj/base.pyc b/jpegObj/base.pyc deleted file mode 100644 index e0df39c..0000000 Binary files a/jpegObj/base.pyc and /dev/null differ diff --git a/jpegObj/compress.py b/jpegObj/compress.py deleted file mode 100644 index 9b6d041..0000000 --- a/jpegObj/compress.py +++ /dev/null @@ -1,183 +0,0 @@ -## -*- coding: utf-8 -*- - - -from pylab import * - -# The standard quantisation tables for JPEG:: - -table0 = array( - [ [ 16, 11, 10, 16, 24, 40, 51, 61 ], - [ 12, 12, 14, 19, 26, 58, 60, 55 ], - [ 14, 13, 16, 24, 40, 57, 69, 56 ], - [ 14, 17, 22, 29, 51, 87, 80, 62 ], - [ 18, 22, 37, 56, 68, 109, 103, 77 ], - [ 24, 35, 55, 64, 81, 104, 113, 92 ], - [ 49, 64, 78, 87, 103, 121, 120, 101 ], - [ 72, 92, 95, 98, 112, 100, 103, 99 ] ] ) - -table1 = array( - [ [ 17, 18, 24, 47, 99, 99, 99, 99 ], - [ 18, 21, 26, 66, 99, 99, 99, 99 ], - [ 24, 26, 56, 99, 99, 99, 99, 99 ], - [ 47, 66, 99, 99, 99, 99, 99, 99 ], - [ 99, 99, 99, 99, 99, 99, 99, 99 ], - [ 99, 99, 99, 99, 99, 99, 99, 99 ], - [ 99, 99, 99, 99, 99, 99, 99, 99 ], - [ 99, 99, 99, 99, 99, 99, 99, 99 ] ] ) - -# The quantTable function seems straight forward, -# but has not been tested. - -def quantTable(quality=50,tnum=0,force_baseline=False): - if quality <= 0: quality = 1 - elif quality > 100: quality = 100 - if quality < 50: quality = 5000 / quality - else: quality = 200 - quality*2 - - t = floor( (t * quality + 50) /100 ) - - t[t<1] = 1 - - if (force_baseline): t[t>255] = 255 - else: t[t>32767] = 32767 # max quantizer needed for 12 bits - - return t - -# I don't think this works. - -def bdctmtx(n=8): - (c,r) = meshgrid(range(n), range(n)) - (c0,r0) = meshgrid(r.flatten()); - (c1,r1) = meshgrid(c.flatten()); - - x = sqrt(float(2) / n) - x *= cos( pi * (2*c + 1) * r / (2 * n)); - x[1,:] = x[1,:] / sqrt(2); - - return x[r0+c0*n+1] * x[r1+c1*n+1] - -def im2vec(im,blksize=8,padsize=0): - """Reshape 2D image blocks into an array of column vectors - - V=im2vec(im,blksize=8,padsize=0) - - IM is an image to be separated into non-overlapping blocks and - reshaped into an MxN array containing N blocks reshaped into Mx1 - column vectors. im2vec is designed to be the inverse of vec2im. - - BLKSIZE is a scalar or 1x2 vector indicating the size of the blocks. - - PADSIZE is a scalar or 1x2 vector indicating the amount of vertical - and horizontal space to be skipped between blocks in the image. - Default is [0 0]. If PADSIZE is a scalar, the same amount of space - is used for both directions. PADSIZE must be non-negative (blocks - must be non-overlapping). - - ROWS indicates the number of rows of blocks found in the image. - COLS indicates the number of columns of blocks found in the image. - """ - - blksize=blksize + array( [0,0] ) - padsize=padsize + array( [0,0] ) - if ( any( padsize < 0 ) ): - raise InputException, "Pad size must be non-negative." - - (height,width) = im.shape - (y,x) = blksize + padsize - - rows = int( ( height + padsize[0] ) / y ) - cols = int( ( width + padsize[1] ) / x ) - - T = zeros( [y*rows,x*cols] ) - - imy = y*rows - padsize[0] - imx = x*cols - padsize[1] - - T[0:imy,0:imx] = im[0:imy,0:imx] - - T = reshape(T, [ cols, y, rows, x ] ) - T = transpose( T, [0,2,1,3]) - T = reshape( T, [ y, x, rows*cols ] ) - V = T[0:blksize[0], 0:blksize[1], 0:(rows*cols) ] - - return (reshape( V, [ rows*cols, y*x ] ), rows, cols) - -def vec2im(V,blksize=None,padsize=0,rows=None,cols=None): - """Reshape and combine column vectors into a 2D image - - V is an MxN array containing N Mx1 column vectors which will be reshaped - and combined to form image IM. - - PADSIZE is a scalar or a 1x2 vector indicating the amount of vertical and - horizontal space to be added as a border between the reshaped vectors. - Default is [0 0]. If PADSIZE is a scalar, the same amount of space is used - for both directions. - - BLKSIZE is a scalar or a 1x2 vector indicating the size of the blocks. - Default is sqrt(M). - - ROWS indicates the number of rows of blocks in the image. Default is - floor(sqrt(N)). - - COLS indicates the number of columns of blocks in the image. Default - is ceil(N/ROWS). - """ - - (n,m) = V.shape - - padsize = padsize + array( [0,0] ) - if ( any( padsize < 0 ) ): - raise InputException, "Pad size must be non-negative." - - if blksize == None: - bsize = floor(sqrt(m)) - - blksize = blksize + array([0,0]) - if prod(blksize) != m: - print m, blksize - raise InputException, 'Block size does not match size of input vectors.' - - if rows == None: - rows = floor(sqrt(n)) - if cols == None: - cols = ceil(n/rows) - -# make image -# -# :: - - (y,x) = blksize + padsize - -# zero() returns float64 and causes T to become a floating point array -# This is a bug; integer input should give integer return -# -# :: - - T = zeros( [rows*cols, y, x] ) - T[0:n, 0:blksize[0], 0:blksize[1]] = \ - reshape( V, [n, blksize[0], blksize[1] ] ) - T = reshape(T, [rows,cols,y,x] ) - T = transpose( T, [0,2,1,3] ) - T = reshape(T, [y*rows,x*cols] ) - return T[0:(y*rows-padsize[0]), 0:(x*cols-padsize[1]) ] - -def quantise(C,Q): - """Quantise DCT coefficients using the quantisation matrix Q.""" - return C / repmat( Q, C.shape / Q.shape ) - -def dequantise(C,Q): - """Dequantise JPEG coefficients using the quantisation matrix Q.""" - return C * repmat( Q, C.shape / Q.shape ) - -def bdct(A,blksize=8): - """Blocked discrete cosine transform for JPEG compression.""" - dctm = bdctmtx(blksize) - (v,r,c) = im2vec(a,blksize) - return vec2im(dot(dctm,v),blksize,rows=r,cols=c) - -def ibdct(A,blksize=8): - """Inverse blocked discrete cosine transform""" - dctm = bdctmtx(blksize) - (v,r,c) = im2vec(a,blksize) - return vec2im(dot(transpose(dctm),v),blksize,rows=r,cols=c) - diff --git a/jpegObj/dct.py b/jpegObj/dct.py deleted file mode 100644 index 74aa372..0000000 --- a/jpegObj/dct.py +++ /dev/null @@ -1,62 +0,0 @@ -## $Id$ -## -*- coding: utf-8 -*- - -# jpeg.dct -# ======== - -from numpy import dot, linalg -import numpy - - -def auxcos(x, u): - return numpy.cos((numpy.pi / 8) * (x + 0.5) * u) - - -def cosmat(M=8, N=8): - C = numpy.array([[auxcos(x, u) for u in range(N)] - for x in range(M)]) / 2 - C[:, 0] = C[:, 0] / numpy.sqrt(2) - # C[0,:] = C[0,:] / numpy.sqrt(2) - return C - - -auxM = cosmat(8, 8) -invM = linalg.inv(auxM) -auxT = numpy.transpose(auxM) -invT = numpy.transpose(invM) - - -def dct2(g): - """ - Perform a 2D DCT transform on g, assuming that g is 8x8. - """ - assert (8, 8) == numpy.shape(g) - return dot(auxT, dot(g, auxM)) - - -def idct2(g): - """ - Perform a 2D inverse DCT transform on g, assuming that g is 8x8. - """ - assert (8, 8) == numpy.shape(g) - # return dot( invM, dot( g, invT ) ) - return dot(invT, dot(g, invM)) - - -def bdct(C, f=dct2): - """ - Make a blockwise (8x8 blocks) 2D DCT transform on the matrix C. - The optional second parameter f specifies the DCT transform function. - The height and width of C have to be divisible by 8. - """ - (M, N) = numpy.shape(C) - assert M % 8 == 0 - assert N % 8 == 0 - S = numpy.ndarray((M, N)) - for i in range(0, M, 8): - for j in range(0, N, 8): - S[i:(i + 8), j:(j + 8)] = f(C[i:(i + 8), j:(j + 8)]) - return S - - -def ibdct(C): return bdct(C, f=idct2) diff --git a/jpegObj/dct.pyc b/jpegObj/dct.pyc deleted file mode 100644 index 1df6868..0000000 Binary files a/jpegObj/dct.pyc and /dev/null differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..f4507a3 --- /dev/null +++ b/main.py @@ -0,0 +1,17 @@ +__author__ = 'chunk' + +from msteg import * +from msteg.steganography import * +from msteg.steganalysis import * + +# f3test = F4.F4(1) +# print f3test.get_key() +# +# ima = Jpeg("res/test3.jpg", key=sample_key) +# +# ciq = ima.coef_arrays[colorMap['Y']] +# +# mpbSteg = MPB.MPB() +# tpm = mpbSteg.get_trans_prob_mat(ciq) + +print sample_key diff --git a/msteg/StegBase.py b/msteg/StegBase.py deleted file mode 100644 index 7afde90..0000000 --- a/msteg/StegBase.py +++ /dev/null @@ -1,169 +0,0 @@ -__author__ = 'chunk' - - -import numpy as np -import time -import os -import sys -import random -import re - -import mjsteg -import jpegObj -from common import * - -sample_key = [46812L, 20559L, 31360L, 16681L, 27536L, 39553L, 5427L, 63029L, 56572L, 36476L, 25695L, 61908L, 63014L, 5908L, 59816L, 56765L] - -class StegBase(object): - """ - This is the base class for some of the JPEG-algorithms that behave - similarly such as JSteg, OutGuess and F3. - """ - - def __init__(self,key=sample_key): - """ - Constructor of the JPEGSteg class. - """ - self.t0 = None - self.cov_jpeg = None - self.cov_data = None - self.hid_data = None - self.key = key - - def _get_cov_data(self, img_path): - """ - Returns DCT coefficients of the cover image. - """ - self.cov_jpeg = jpegObj.Jpeg(img_path) - self.key = self.cov_jpeg.getkey() - self.cov_data = self.cov_jpeg.getCoefBlocks() - return self.cov_data - - - def _get_hid_data(self, src_hidden): - """ - Returnsthe secret data as byte sequence. - """ - raw = [0, 0, 0, 0] + np.fromfile(src_hidden, np.uint8).tolist() - raw_size = len(raw) - for i in xrange(4): - raw[i] = raw_size % 256 - raw_size /= 256 - self.hid_data = np.array(raw, dtype=np.uint8) - - if np.size(self.hid_data) * 8 > np.size(self.cov_data): - raise Exception("Cover image is too small to embed data.Cannot fit %d bits in %d DCT coefficients" % ( - np.size(self.hid_data) * 8, np.size(self.cov_data))) - return self.hid_data - - - def _post_embed_actions(self, src_cover, src_hidden, tgt_stego): - """ - This function isn't named very accurately. It actually calls the - _raw_embed function in inherited classes. - """ - try: - cov_data = self._get_cov_data(src_cover) - hid_data = self._get_hid_data(src_hidden) - # print hid_data.dtype,type(hid_data),hid_data.tolist() - cov_data, bits_cnt = self._raw_embed(cov_data, hid_data) - - if bits_cnt != np.size(hid_data) * 8: - raise Exception("Expected embedded size is %db but actually %db." % ( - np.size(hid_data) * 8, bits_cnt)) - - self.cov_jpeg.setCoefBlocks(cov_data) - self.cov_jpeg.Jwrite(tgt_stego) - - # size_cov = os.path.getsize(tgt_stego) - size_cov = np.size(cov_data) / 8 - size_embedded = np.size(hid_data) - - self._display_stats("embedded", size_cov, size_embedded, - time.time() - self.t0) - - except TypeError as e: - raise e - except Exception as expt: - print "Exception when embedding!" - raise - - def _post_extract_actions(self, src_steg, tgt_hidden): - """ - This function isn't named very accurately. It actually calls the - _raw_extract function in inherited classes. - """ - try: - steg_data = self._get_cov_data(src_steg) - # emb_size = os.path.getsize(src_steg) - emb_size = np.size(steg_data) / 8 - - - # recovering file size - header_size = 4 * 8 - size_data, bits_cnt = self._raw_extract(steg_data, header_size) - if bits_cnt < header_size: - raise Exception("Expected embedded size is %db but actually %db." % ( - header_size, bits_cnt)) - - size_data = bits2bytes(size_data) - size_hd = 0 - for i in xrange(4): - size_hd += size_data[i] * 256 ** i - - raw_size = size_hd * 8 - - if raw_size > np.size(steg_data): - raise Exception("Supposed secret data too large for stego image.") - - hid_data, bits_cnt = self._raw_extract(steg_data, raw_size) - - if bits_cnt != raw_size: - raise Exception("Expected embedded size is %db but actually %db." % ( - raw_size, bits_cnt)) - - hid_data = bits2bytes(hid_data) - # print hid_data.dtype,type(hid_data),hid_data.tolist() - hid_data[4:].tofile(tgt_hidden) - - self._display_stats("extracted", emb_size, - np.size(hid_data), - time.time() - self.t0) - except Exception as expt: - print "Exception when extracting!" - raise - - - def _looks_like_jpeg(self, path): - try: - with open(path, 'r') as f: - return f.read(2) == '\xff\xd8' - except IOError: - return False - - def _display_stats(self, verb, cov_size, emb_size, duration): - print( - "%dB %s in %.2fs (%.2f kBps), embedding ratio: %.4f" % - (emb_size, verb, duration, (emb_size / duration) / 1000., - float(emb_size) / cov_size)) - - # set & get - def set_key(self, key): - assert key != None - self.key = key - - def get_key(self): - return self.key - - # dummy functions to please pylint - def _raw_embed(self, cov_data, hid_data): - pass - - def _raw_extract(self, steg_data, num_bits): - pass - - def _dummy_embed_hook(self, cov_data, hid_data): - pass - - def _dummy_extract_hook(self, steg_data, num_bits): - pass diff --git a/msteg/StegBase.pyc b/msteg/StegBase.pyc deleted file mode 100644 index 159c4cb..0000000 Binary files a/msteg/StegBase.pyc and /dev/null differ diff --git a/msteg/__init__.py b/msteg/__init__.py index 345c62e..62b35dd 100644 --- a/msteg/__init__.py +++ b/msteg/__init__.py @@ -1,3 +1,172 @@ __author__ = 'chunk' -from .steganography import * -from .steganalysis import * + +import numpy as np +import time +import os +import sys +import random +import re + +import mjsteg +import pyjpegobj +from common import * + +__all__ = ['StegBase', 'sample_key'] + +sample_key = [46812L, 20559L, 31360L, 16681L, 27536L, 39553L, 5427L, 63029L, 56572L, 36476L, 25695L, 61908L, 63014L, + 5908L, 59816L, 56765L] + + +class StegBase(object): + """ + This is the base class for some of the JPEG-algorithms that behave + similarly such as JSteg, OutGuess and F3. + """ + + def __init__(self, key=sample_key): + """ + Constructor of the JPEGSteg class. + """ + self.t0 = None + self.cov_jpeg = None + self.cov_data = None + self.hid_data = None + self.key = key + + def _get_cov_data(self, img_path): + """ + Returns DCT coefficients of the cover image. + """ + self.cov_jpeg = pyjpegobj.Jpeg(img_path) + self.key = self.cov_jpeg.getkey() + self.cov_data = self.cov_jpeg.getCoefBlocks() + return self.cov_data + + + def _get_hid_data(self, src_hidden): + """ + Returnsthe secret data as byte sequence. + """ + raw = [0, 0, 0, 0] + np.fromfile(src_hidden, np.uint8).tolist() + raw_size = len(raw) + for i in xrange(4): + raw[i] = raw_size % 256 + raw_size /= 256 + self.hid_data = np.array(raw, dtype=np.uint8) + + if np.size(self.hid_data) * 8 > np.size(self.cov_data): + raise Exception("Cover image is too small to embed data.Cannot fit %d bits in %d DCT coefficients" % ( + np.size(self.hid_data) * 8, np.size(self.cov_data))) + return self.hid_data + + + def _post_embed_actions(self, src_cover, src_hidden, tgt_stego): + """ + This function isn't named very accurately. It actually calls the + _raw_embed function in inherited classes. + """ + try: + cov_data = self._get_cov_data(src_cover) + hid_data = self._get_hid_data(src_hidden) + # print hid_data.dtype,type(hid_data),hid_data.tolist() + cov_data, bits_cnt = self._raw_embed(cov_data, hid_data) + + if bits_cnt != np.size(hid_data) * 8: + raise Exception("Expected embedded size is %db but actually %db." % ( + np.size(hid_data) * 8, bits_cnt)) + + self.cov_jpeg.setCoefBlocks(cov_data) + self.cov_jpeg.Jwrite(tgt_stego) + + # size_cov = os.path.getsize(tgt_stego) + size_cov = np.size(cov_data) / 8 + size_embedded = np.size(hid_data) + + self._display_stats("embedded", size_cov, size_embedded, + time.time() - self.t0) + + except TypeError as e: + raise e + except Exception as expt: + print "Exception when embedding!" + raise + + def _post_extract_actions(self, src_steg, tgt_hidden): + """ + This function isn't named very accurately. It actually calls the + _raw_extract function in inherited classes. + """ + try: + steg_data = self._get_cov_data(src_steg) + # emb_size = os.path.getsize(src_steg) + emb_size = np.size(steg_data) / 8 + + + # recovering file size + header_size = 4 * 8 + size_data, bits_cnt = self._raw_extract(steg_data, header_size) + if bits_cnt < header_size: + raise Exception("Expected embedded size is %db but actually %db." % ( + header_size, bits_cnt)) + + size_data = bits2bytes(size_data) + size_hd = 0 + for i in xrange(4): + size_hd += size_data[i] * 256 ** i + + raw_size = size_hd * 8 + + if raw_size > np.size(steg_data): + raise Exception("Supposed secret data too large for stego image.") + + hid_data, bits_cnt = self._raw_extract(steg_data, raw_size) + + if bits_cnt != raw_size: + raise Exception("Expected embedded size is %db but actually %db." % ( + raw_size, bits_cnt)) + + hid_data = bits2bytes(hid_data) + # print hid_data.dtype,type(hid_data),hid_data.tolist() + hid_data[4:].tofile(tgt_hidden) + + self._display_stats("extracted", emb_size, + np.size(hid_data), + time.time() - self.t0) + except Exception as expt: + print "Exception when extracting!" + raise + + + def _looks_like_jpeg(self, path): + try: + with open(path, 'r') as f: + return f.read(2) == '\xff\xd8' + except IOError: + return False + + def _display_stats(self, verb, cov_size, emb_size, duration): + print( + "%dB %s in %.2fs (%.2f kBps), embedding ratio: %.4f" % + (emb_size, verb, duration, (emb_size / duration) / 1000., + float(emb_size) / cov_size)) + + # set & get + def set_key(self, key): + assert key != None + self.key = key + + def get_key(self): + return self.key + + # dummy functions to please pylint + def _raw_embed(self, cov_data, hid_data): + pass + + def _raw_extract(self, steg_data, num_bits): + pass + + def _dummy_embed_hook(self, cov_data, hid_data): + pass + + def _dummy_extract_hook(self, steg_data, num_bits): + pass diff --git a/msteg/__init__.pyc b/msteg/__init__.pyc index 8c3fbba..2aee3c5 100644 Binary files a/msteg/__init__.pyc and b/msteg/__init__.pyc differ diff --git a/msteg/steganalysis/ChiSquare.py b/msteg/steganalysis/ChiSquare.py index 43f8b67..88bdfdc 100644 --- a/msteg/steganalysis/ChiSquare.py +++ b/msteg/steganalysis/ChiSquare.py @@ -21,9 +21,8 @@ import numpy from scipy.stats import chisquare import matplotlib.pyplot as plt import itertools as it -from msteg.StegBase import StegBase -from msteg.StegBase import * +from msteg import * class ChiSquare(StegBase): @@ -35,10 +34,6 @@ class ChiSquare(StegBase): self.ui = ui self.core = core - @describe_and_annotate((None, None), - ("Source image", ImagePath), - ("Target image", NewFilePath), - ("2nd Target image", NewFilePath)) def detect(self, src, tgt, tgt2): """
@@ -81,7 +76,10 @@ class ChiSquare(StegBase): # ---------------------------- Algorithm ------------------------------ # Build DCT-histogram in steps of \approx 1% of all coefficients and # calculate the p-value at each step. - dct_data = rw_dct.read_dct_coefficients(src) + + # dct_data = rw_dct.read_dct_coefficients(src) + dct_data = self._get_cov_data(src) + hist = defaultdict(int) cnt = 0 l = len(dct_data) @@ -150,7 +148,7 @@ of the file capacity.') color = (int(r * 2 * 255), 255, 0) cnt2 += 1 img2.paste(color, (left, top, min(left + 8, width), - min(top + 8, height))) + min(top + 8, height))) self.core.media_manager.put_media(tgt2, img2) def __str__(self): diff --git a/msteg/steganalysis/ChiSquare.pyc b/msteg/steganalysis/ChiSquare.pyc new file mode 100644 index 0000000..d59a6b1 Binary files /dev/null and b/msteg/steganalysis/ChiSquare.pyc differ diff --git a/msteg/steganalysis/MPB.py b/msteg/steganalysis/MPB.py index b7d7d44..751f484 100644 --- a/msteg/steganalysis/MPB.py +++ b/msteg/steganalysis/MPB.py @@ -6,9 +6,9 @@ Yun Q. Shi, et al - A Markov Process Based Approach to Effective Attacking JPEG import time import math import numpy as np -from msteg.StegBase import * +from msteg import * import mjsteg -import jpegObj +import pyjpegobj from common import * import csv @@ -238,8 +238,8 @@ class MPB(StegBase): with open('res/tmp.model', 'rb') as modelfile: clf = pickle.load(modelfile) - im = jpegObj.Jpeg(image, key=sample_key) - ciq = im.coef_arrays[jpegObj.colorMap['Y']] + im = pyjpegobj.Jpeg(image, key=sample_key) + ciq = im.coef_arrays[pyjpegobj.colorMap['Y']] tpm = self.get_trans_prob_mat(ciq) return clf.predict(tpm) @@ -269,8 +269,8 @@ class MPB(StegBase): svm = cv2.SVM() svm.load('res/svm_data.model') - im = jpegObj.Jpeg(image, key=sample_key) - ciq = im.coef_arrays[jpegObj.colorMap['Y']] + im = pyjpegobj.Jpeg(image, key=sample_key) + ciq = im.coef_arrays[pyjpegobj.colorMap['Y']] tpm = self.get_trans_prob_mat(ciq) return svm.predict(tpm) @@ -279,7 +279,7 @@ class MPB(StegBase): X, Y = self.load_dataset('local', 'images_map_Train.tsv') return self._model_svm_train_sk(X, Y) - def predict_svm(self,image): + def predict_svm(self, image): return self._model_svm_predict_sk(image) diff --git a/msteg/steganalysis/MPB.pyc b/msteg/steganalysis/MPB.pyc index d1d23cf..9e4bb65 100644 Binary files a/msteg/steganalysis/MPB.pyc and b/msteg/steganalysis/MPB.pyc differ diff --git a/msteg/steganalysis/__init__.py b/msteg/steganalysis/__init__.py index a1459cf..8ddc530 100644 --- a/msteg/steganalysis/__init__.py +++ b/msteg/steganalysis/__init__.py @@ -1 +1,3 @@ __author__ = 'chunk' + +__all__ = ['ChiSquare', 'MPB'] diff --git a/msteg/steganalysis/__init__.pyc b/msteg/steganalysis/__init__.pyc index 72cd600..1c7cbb1 100644 Binary files a/msteg/steganalysis/__init__.pyc and b/msteg/steganalysis/__init__.pyc differ diff --git a/msteg/steganography/F3.py b/msteg/steganography/F3.py index cfbc781..d0e45b7 100644 --- a/msteg/steganography/F3.py +++ b/msteg/steganography/F3.py @@ -4,7 +4,7 @@ __author__ = 'chunk' import time import math import numpy as np -from msteg.StegBase import StegBase +from msteg import * from common import * diff --git a/msteg/steganography/F3.pyc b/msteg/steganography/F3.pyc index f780cb4..578c3e1 100644 Binary files a/msteg/steganography/F3.pyc and b/msteg/steganography/F3.pyc differ diff --git a/msteg/steganography/F4-simple.py b/msteg/steganography/F4-simple.py index 544932c..5299717 100644 --- a/msteg/steganography/F4-simple.py +++ b/msteg/steganography/F4-simple.py @@ -11,7 +11,7 @@ which is not included in the original description of F4. """ import time import numpy as np -from msteg.StegBase import StegBase +from msteg import * from common import * diff --git a/msteg/steganography/F4-simple.pyc b/msteg/steganography/F4-simple.pyc new file mode 100644 index 0000000..db0be10 Binary files /dev/null and b/msteg/steganography/F4-simple.pyc differ diff --git a/msteg/steganography/F4.py b/msteg/steganography/F4.py index a6e47dc..7cc183a 100644 --- a/msteg/steganography/F4.py +++ b/msteg/steganography/F4.py @@ -3,9 +3,9 @@ __author__ = 'chunk' import time import numpy as np import numpy.random as rnd -from msteg.StegBase import * +from msteg import * import mjsteg -import jpegObj +import pyjpegobj from common import * @@ -24,7 +24,7 @@ class F4(StegBase): """ Returns DCT coefficients of the cover image. """ - self.cov_jpeg = jpegObj.Jpeg(img_path, key=self.key) + self.cov_jpeg = pyjpegobj.Jpeg(img_path, key=self.key) cov_data = self.cov_jpeg.getsignal(channel='Y') self.cov_data = np.array(cov_data, dtype=np.int16) diff --git a/msteg/steganography/F4.pyc b/msteg/steganography/F4.pyc index 4de9940..36caacb 100644 Binary files a/msteg/steganography/F4.pyc and b/msteg/steganography/F4.pyc differ diff --git a/msteg/steganography/F5.py b/msteg/steganography/F5.py index d1eecb2..760047d 100644 --- a/msteg/steganography/F5.py +++ b/msteg/steganography/F5.py @@ -27,10 +27,10 @@ import time import math import numpy as np import numpy.random as rnd -from msteg.StegBase import * +from msteg import * from F4 import F4 import mjsteg -import jpegObj +import pyjpegobj from common import * @@ -55,7 +55,7 @@ class F5(StegBase): """ Returns DCT coefficients of the cover image. """ - self.cov_jpeg = jpegObj.Jpeg(img_path, key=self.key) + self.cov_jpeg = pyjpegobj.Jpeg(img_path, key=self.key) cov_data = self.cov_jpeg.getsignal(channel='Y') self.cov_data = np.array(cov_data, dtype=np.int16) diff --git a/msteg/steganography/F5.pyc b/msteg/steganography/F5.pyc index 95c5a18..d5dfe84 100644 Binary files a/msteg/steganography/F5.pyc and b/msteg/steganography/F5.pyc differ diff --git a/msteg/steganography/LSB.py b/msteg/steganography/LSB.py index 5be5721..00b255f 100644 --- a/msteg/steganography/LSB.py +++ b/msteg/steganography/LSB.py @@ -1,10 +1,9 @@ __author__ = 'chunk' - import time import numpy as np import scipy as sp -from msteg.StegBase import StegBase +from msteg import * from common import * diff --git a/msteg/steganography/LSB.pyc b/msteg/steganography/LSB.pyc index 4e40223..a30e5b7 100644 Binary files a/msteg/steganography/LSB.pyc and b/msteg/steganography/LSB.pyc differ diff --git a/msteg/steganography/__init__.py b/msteg/steganography/__init__.py index a1459cf..5b3d36a 100644 --- a/msteg/steganography/__init__.py +++ b/msteg/steganography/__init__.py @@ -1 +1,3 @@ __author__ = 'chunk' + +__all__ = ['LSB', 'F3', 'F4', 'F4-simple', 'F5'] diff --git a/msteg/steganography/__init__.pyc b/msteg/steganography/__init__.pyc index de73549..0d5bae9 100644 Binary files a/msteg/steganography/__init__.pyc and b/msteg/steganography/__init__.pyc differ diff --git a/pyjpegobj/__init__.py b/pyjpegobj/__init__.py new file mode 100644 index 0000000..2437107 --- /dev/null +++ b/pyjpegobj/__init__.py @@ -0,0 +1,406 @@ +## -*- coding: utf-8 -*- + +from mjsteg import Jsteg + +__all__ = ['Jpeg','colorMap','diffblock','diffblocks'] + +# We need standard components from :mod:`numpy`, and some auxiliary +# functions from submodules. +# +# :: + +import numpy.random as rnd +from numpy import shape +import numpy as np +import pylab as plt + +import base +from dct import bdct, ibdct + +# The colour codes are defined in the JPEG standard. We store +# them here for easy reference by name:: + +colorCode = { + "GRAYSCALE": 1, + "RGB": 2, + "YCbCr": 3, + "CMYK": 4, + "YCCK": 5 +} + +colorParam = ['Y', 'Cb', 'Cr'] +colorMap = {'Y': 0, 'Cb': 1, 'Cr': 2} + +# The JPEG class +# ============== + +class Jpeg(Jsteg): + """ + The jpeg (derived from jpegObject) allows the user to extract + a sequence of pseudo-randomly ordered jpeg coefficients for + watermarking/steganography, and reinsert them. + """ + + def __init__(self, file=None, key=None, rndkey=True, image=None, + verbosity=1, **kw): + """ + The constructor will return a new Object with data from the given file. + + The key is used to determine the order of the jpeg coefficients. + If no key is given, a random key is extracted using + random.SystemRandom(). + """ + if image != None: + raise NotImplementedError, "Compression is not yet implemented" + Jsteg.__init__(self, file, **kw) + self.verbosity = verbosity + if verbosity > 0: + print "[Jpeg.__init__] Image size %ix%i" % (self.coef_arrays[0].shape) + if key != None: + self.key = key + elif rndkey: + self.key = [base.sysrnd.getrandbits(16) for x in range(16)] + else: + self.key = None + + + def getkey(self): + """Return the key used to shuffle the coefficients.""" + return self.key + + # 1D Signal Representations + # ------------------------- + + def rawsignal(self, mask=base.acMaskBlock, channel="All"): + """ + Return a 1D array of AC coefficients. + (Most applications should use getsignal() rather than rawsignal().) + """ + R = [] + if channel == "All": + for X in self.coef_arrays: + (h, w) = X.shape + A = base.acMask(h, w, mask) + R = np.hstack([R, X[A]]) + else: + cID = self.getCompID(channel) + X = self.coef_arrays[cID] + (h, w) = X.shape + A = base.acMask(h, w, mask) + R = np.hstack([R, X[A]]) + return R + + def getsignal(self, mask=base.acMaskBlock, channel="All"): + """Return a 1D array of AC coefficients in random order.""" + R = self.rawsignal(mask, channel) + if self.key == None: + return R + else: + rnd.seed(self.key) + return R[rnd.permutation(len(R))] + + def setsignal(self, R0, mask=base.acMaskBlock, channel="All"): + """Reinserts AC coefficients from getitem in the correct positions.""" + if self.key != None: + rnd.seed(self.key) + fst = 0 + P = rnd.permutation(len(R0)) + R = np.array(R0) + R[P] = R0 + else: + R = R0 + if channel == "All": + for cID in range(3): + X = self.coef_arrays[cID] + s = X.size * 63 / 64 + (h, w) = X.shape + X[base.acMask(h, w, mask)] = R[fst:(fst + s)] + fst += s + + # Jset + blocks = self.getCoefBlocks(channel=colorParam[cID]) + xmax, ymax = self.Jgetcompdim(cID) + for y in range(ymax): + for x in range(xmax): + block = blocks[y, x] + self.Jsetblock(x, y, cID, bytearray(block.astype(np.int16))) + + else: + cID = self.getCompID(channel) + X = self.coef_arrays[cID] + s = X.size * 63 / 64 + (h, w) = X.shape + X[base.acMask(h, w, mask)] = R[fst:(fst + s)] + fst += s + + # Jset + blocks = self.getCoefBlocks(channel) + xmax, ymax = self.Jgetcompdim(cID) + for y in range(ymax): + for x in range(xmax): + block = blocks[y, x] + self.Jsetblock(x, y, cID, bytearray(block.astype(np.int16))) + + assert len(R) == fst + + + # Histogram and Image Statistics + # ------------------------------ + + def abshist(self, mask=base.acMaskBlock, T=8): + """ + Make a histogram of absolute values for a signal. + """ + A = abs(self.rawsignal(mask)).tolist() + L = len(A) + D = {} + C = 0 + for i in range(T + 1): + D[i] = A.count(i) + C += D[i] + D["high"] = L - C + D["total"] = L + return D + + def hist(self, mask=base.acMaskBlock, T=8): + """ + Make a histogram of the jpeg coefficients. + The mask is a boolean 8x8 matrix indicating the + frequencies to be included. This defaults to the + AC coefficients. + """ + A = self.rawsignal(mask).tolist() + E = [-np.inf] + [i for i in range(-T, T + 2)] + [np.inf] + return np.histogram(A, E) + + def plotHist(self, mask=base.acMaskBlock, T=8): + """ + Make a histogram of the jpeg coefficients. + The mask is a boolean 8x8 matrix indicating the + frequencies to be included. This defaults to the + AC coefficients. + """ + A = self.rawsignal(mask).tolist() + E = [i for i in range(-T, T + 2)] + plt.hist(A, E, histtype='bar') + plt.show() + + def nzcount(self, *a, **kw): + """Number of non-zero AC coefficients. + + Arguments are passed to rawsignal(), so a non-default mask could + be specified to get other coefficients than the 63 AC coefficients. + """ + R = list(self.rawsignal(*a, **kw)) + return len(R) - R.count(0) + + # Access to JPEG Image Data + # ------------------------- + + def getCompID(self, channel): + """ + Get the index of the given colour channel. + """ + # How do we adress different channels? + colourSpace = self.jpeg_color_space; + if colourSpace == colorCode["GRAYSCALE"]: + if channel == "Y": + return 0 + elif channel == None: + return 0 + else: + raise Exception, "Invalid colour space designator" + elif colourSpace == colorCode["YCbCr"]: + if channel == "Y": + return 0 + elif channel == "Cb": + return 1 + elif channel == "Cr": + return 2 + else: + raise Exception, "Invalid colour space designator" + raise NotImplementedError, "Only YCbCr and Grayscale are supported." + + def getQMatrix(self, channel): + """ + Return the quantisation matrix for the given colour channel. + """ + cID = self.getCompID(channel) + return self.quant_tables[self.comp_info[cID]["quant_tbl_no"]] + + def getCoefMatrix(self, channel="Y"): + """ + This method returns the coefficient matrix for the given + colour channel (as a matrix). + """ + cID = self.getCompID(channel) + return self.coef_arrays[cID] + + def setCoefMatrix(self, matrix, channel="Y"): + v, h = self.getCoefMatrix(channel).shape + assert matrix.shape == (v, h), "matrix is expected of size (%d,%d)" % (v, h) + + cID = self.getCompID(channel) + self.coef_arrays[cID] = matrix + + blocks = self.getCoefBlocks(channel) + xmax, ymax = self.Jgetcompdim(cID) + for y in range(ymax): + for x in range(xmax): + block = blocks[y, x] + self.Jsetblock(x, y, cID, bytearray(block.astype(np.int16))) + + def getCoefBlocks(self, channel="Y"): + """ + This method returns the coefficient matrix for the given + colour channel (as a 4-D tensor: (v,h,row,col)). + """ + if channel == "All": + return [ + np.array([np.hsplit(arr, arr.shape[1] / 8) for arr in np.vsplit(compMat, compMat.shape[0] / 8)]) + for compMat in self.coef_arrays] + + compMat = self.getCoefMatrix(channel="Y") + return np.array([np.hsplit(arr, arr.shape[1] / 8) for arr in np.vsplit(compMat, compMat.shape[0] / 8)]) + + def getCoefBlock(self, channel="Y", loc=(0, 0)): + """ + This method returns the coefficient matrix for the given + colour channel (as a 4-D tensor: (v,h,row,col)). + """ + return self.getCoefBlocks(channel)[loc] + + + def setCoefBlock(self, block, channel="Y", loc=(0, 0)): + assert block.shape == (8, 8), "block is expected of size (8,8)" + cID = self.getCompID(channel) + v, h = loc[0] * 8, loc[1] * 8 + self.coef_arrays[cID][v:v + 8, h:h + 8] = block + + self.Jsetblock(loc[1], loc[0], cID, bytearray(block.astype(np.int16))) + + def setCoefBlocks(self, blocks, channel="Y"): + assert blocks.shape[-2:] == (8, 8), "block is expected of size (8,8)" + cID = self.getCompID(channel) + + vmax, hmax = blocks.shape[:2] + for i in range(vmax): + for j in range(hmax): + v, h = i * 8, j * 8 + self.coef_arrays[cID][v:v + 8, h:h + 8] = blocks[i, j] + self.Jsetblock(j, i, cID, bytearray(blocks[i, j].astype(np.int16))) + + # Decompression + # ------------- + + def getSpatial(self, channel="Y"): + """ + This method returns one decompressed colour channel as a matrix. + The appropriate JPEG coefficient matrix is dequantised + (using the quantisation tables held by the object) and + inverse DCT transformed. + """ + X = self.getCoefMatrix(channel) + Q = self.getQMatrix(channel) + (M, N) = shape(X) + assert M % 8 == 0, "Image size not divisible by 8" + assert N % 8 == 0, "Image size not divisible by 8" + D = X * base.repmat(Q, (M / 8, N / 8)) + S = ibdct(D) + # assert max( abs(S).flatten() ) <=128, "Image colours out of range" + return (S + 128 ).astype(np.uint8) + + # Complete, general decompression is not yet implemented:: + + def getimage(self): + """ + Decompress the image and a PIL Image object. + """ + + # Probably better to use a numpy image/array. + + raise NotImplementedError, "Decompression is not yet implemented" + + # We miss the routines for upsampling and adjusting the size + + L = len(self.coef_arrays) + im = [] + for i in range(L): + C = self.coef_arrays[i] + if C != None: + Q = self.quant_tables[self.comp_info[i]["quant_tbl_no"]] + im.append(ibdct(dequantise(C, Q))) + return Image.fromarray(im) + + + # Calibration + # ----------- + + def getCalibrated(self, channel="Y", mode="all"): + """ + Return a calibrated coefficient matrix for the given channel. + Channel may be "Y", "Cb", or "Cr" for YCbCr format. + For Grayscale images, it may be None or "Y". + """ + S = self.getSpatial(channel) + (M, N) = shape(S) + assert M % 8 == 0, "Image size not divisible by 8" + assert N % 8 == 0, "Image size not divisible by 8" + if mode == "col": + S1 = S[:, 4:(N - 4)] + cShape = ( M / 8, N / 8 - 1 ) + else: + S1 = S[4:(M - 4), 4:(N - 4)] + cShape = ( (M - 1) / 8, (N - 1) / 8 ) + D = bdct(S1 - 128) + X = D / base.repmat(self.getQMatrix(channel), cShape) + return np.round(X) + + def calibrate(self, *a, **kw): + assert len(self.coef_arrays) == 1 + self.coef_arrays[0] = self.getCalibrated(*a, **kw) + + def getCalSpatial(self, channel="Y"): + """ + Return the decompressed, calibrated, grayscale image. + A different colour channel can be selected with the channel + argument. + """ + + # We calibrate the image, obtaining a JPEG matrix. + + C = self.getCalibrated(channel) + + # The rest is straight forward JPEG decompression. + + (M, N) = shape(C) + cShape = (M / 8, N / 8) + D = C * base.repmat(self.getQMatrix(channel), cShape) + S = np.round(ibdct(D) + 128) + return S.astype(np.uint8) + + +def diffblock(c1, c2): + diff = False + if np.array_equal(c1, c2): + print("blocks match") + else: + print("blocks not match") + diff = True + + return diff + + +def diffblocks(a, b): + diff = False + cnt = 0 + for comp in range(a.image_components): + xmax, ymax = a.Jgetcompdim(comp) + for y in range(ymax): + for x in range(xmax): + if a.Jgetblock(x, y, comp) != b.Jgetblock(x, y, comp): + print("blocks({},{}) in component {} not match".format(y, x, comp)) + diff = True + cnt += 1 + return diff, cnt + diff --git a/pyjpegobj/__init__.pyc b/pyjpegobj/__init__.pyc new file mode 100644 index 0000000..fb36ad3 Binary files /dev/null and b/pyjpegobj/__init__.pyc differ diff --git a/pyjpegobj/base.py b/pyjpegobj/base.py new file mode 100644 index 0000000..728a59b --- /dev/null +++ b/pyjpegobj/base.py @@ -0,0 +1,38 @@ +## -*- coding: utf-8 -*- + +from numpy import array, vstack, hstack, kron, ones +from random import SystemRandom + +sysrnd = SystemRandom() + + +def uMask(u=7): + return array([[(x + y < u) & (x + y > 0) for x in range(8)] for y in range(8)]) + + +acMaskBlock = array( + [[False] + [True for x in range(7)]] + + [[True for x in range(8)] for y in range(7)] +) + + +def acMask(h1, w1, mask=acMaskBlock): + """Return a mask of the given size for the AC coefficients of a + JPEG coefficient matrix.""" + (h, w) = (h1 / 8, w1 / 8) + A = vstack([mask for x in range(h)]) + A = hstack([A for x in range(w)]) + return A + + +def repmat(M, rep): return kron(ones(rep), M) + + +def getfreq(A, i, j): + """ + Return a submatrix of a JPEG matrix A including only frequency (i,j) + from each block. + """ + (M, N) = A.shape + return A[xrange(i, M, 8), :][:, xrange(j, N, 8)] + diff --git a/pyjpegobj/base.pyc b/pyjpegobj/base.pyc new file mode 100644 index 0000000..8a478e7 Binary files /dev/null and b/pyjpegobj/base.pyc differ diff --git a/pyjpegobj/compress.py b/pyjpegobj/compress.py new file mode 100644 index 0000000..9b6d041 --- /dev/null +++ b/pyjpegobj/compress.py @@ -0,0 +1,183 @@ +## -*- coding: utf-8 -*- + + +from pylab import * + +# The standard quantisation tables for JPEG:: + +table0 = array( + [ [ 16, 11, 10, 16, 24, 40, 51, 61 ], + [ 12, 12, 14, 19, 26, 58, 60, 55 ], + [ 14, 13, 16, 24, 40, 57, 69, 56 ], + [ 14, 17, 22, 29, 51, 87, 80, 62 ], + [ 18, 22, 37, 56, 68, 109, 103, 77 ], + [ 24, 35, 55, 64, 81, 104, 113, 92 ], + [ 49, 64, 78, 87, 103, 121, 120, 101 ], + [ 72, 92, 95, 98, 112, 100, 103, 99 ] ] ) + +table1 = array( + [ [ 17, 18, 24, 47, 99, 99, 99, 99 ], + [ 18, 21, 26, 66, 99, 99, 99, 99 ], + [ 24, 26, 56, 99, 99, 99, 99, 99 ], + [ 47, 66, 99, 99, 99, 99, 99, 99 ], + [ 99, 99, 99, 99, 99, 99, 99, 99 ], + [ 99, 99, 99, 99, 99, 99, 99, 99 ], + [ 99, 99, 99, 99, 99, 99, 99, 99 ], + [ 99, 99, 99, 99, 99, 99, 99, 99 ] ] ) + +# The quantTable function seems straight forward, +# but has not been tested. + +def quantTable(quality=50,tnum=0,force_baseline=False): + if quality <= 0: quality = 1 + elif quality > 100: quality = 100 + if quality < 50: quality = 5000 / quality + else: quality = 200 - quality*2 + + t = floor( (t * quality + 50) /100 ) + + t[t<1] = 1 + + if (force_baseline): t[t>255] = 255 + else: t[t>32767] = 32767 # max quantizer needed for 12 bits + + return t + +# I don't think this works. + +def bdctmtx(n=8): + (c,r) = meshgrid(range(n), range(n)) + (c0,r0) = meshgrid(r.flatten()); + (c1,r1) = meshgrid(c.flatten()); + + x = sqrt(float(2) / n) + x *= cos( pi * (2*c + 1) * r / (2 * n)); + x[1,:] = x[1,:] / sqrt(2); + + return x[r0+c0*n+1] * x[r1+c1*n+1] + +def im2vec(im,blksize=8,padsize=0): + """Reshape 2D image blocks into an array of column vectors + + V=im2vec(im,blksize=8,padsize=0) + + IM is an image to be separated into non-overlapping blocks and + reshaped into an MxN array containing N blocks reshaped into Mx1 + column vectors. im2vec is designed to be the inverse of vec2im. + + BLKSIZE is a scalar or 1x2 vector indicating the size of the blocks. + + PADSIZE is a scalar or 1x2 vector indicating the amount of vertical + and horizontal space to be skipped between blocks in the image. + Default is [0 0]. If PADSIZE is a scalar, the same amount of space + is used for both directions. PADSIZE must be non-negative (blocks + must be non-overlapping). + + ROWS indicates the number of rows of blocks found in the image. + COLS indicates the number of columns of blocks found in the image. + """ + + blksize=blksize + array( [0,0] ) + padsize=padsize + array( [0,0] ) + if ( any( padsize < 0 ) ): + raise InputException, "Pad size must be non-negative." + + (height,width) = im.shape + (y,x) = blksize + padsize + + rows = int( ( height + padsize[0] ) / y ) + cols = int( ( width + padsize[1] ) / x ) + + T = zeros( [y*rows,x*cols] ) + + imy = y*rows - padsize[0] + imx = x*cols - padsize[1] + + T[0:imy,0:imx] = im[0:imy,0:imx] + + T = reshape(T, [ cols, y, rows, x ] ) + T = transpose( T, [0,2,1,3]) + T = reshape( T, [ y, x, rows*cols ] ) + V = T[0:blksize[0], 0:blksize[1], 0:(rows*cols) ] + + return (reshape( V, [ rows*cols, y*x ] ), rows, cols) + +def vec2im(V,blksize=None,padsize=0,rows=None,cols=None): + """Reshape and combine column vectors into a 2D image + + V is an MxN array containing N Mx1 column vectors which will be reshaped + and combined to form image IM. + + PADSIZE is a scalar or a 1x2 vector indicating the amount of vertical and + horizontal space to be added as a border between the reshaped vectors. + Default is [0 0]. If PADSIZE is a scalar, the same amount of space is used + for both directions. + + BLKSIZE is a scalar or a 1x2 vector indicating the size of the blocks. + Default is sqrt(M). + + ROWS indicates the number of rows of blocks in the image. Default is + floor(sqrt(N)). + + COLS indicates the number of columns of blocks in the image. Default + is ceil(N/ROWS). + """ + + (n,m) = V.shape + + padsize = padsize + array( [0,0] ) + if ( any( padsize < 0 ) ): + raise InputException, "Pad size must be non-negative." + + if blksize == None: + bsize = floor(sqrt(m)) + + blksize = blksize + array([0,0]) + if prod(blksize) != m: + print m, blksize + raise InputException, 'Block size does not match size of input vectors.' + + if rows == None: + rows = floor(sqrt(n)) + if cols == None: + cols = ceil(n/rows) + +# make image +# +# :: + + (y,x) = blksize + padsize + +# zero() returns float64 and causes T to become a floating point array +# This is a bug; integer input should give integer return +# +# :: + + T = zeros( [rows*cols, y, x] ) + T[0:n, 0:blksize[0], 0:blksize[1]] = \ + reshape( V, [n, blksize[0], blksize[1] ] ) + T = reshape(T, [rows,cols,y,x] ) + T = transpose( T, [0,2,1,3] ) + T = reshape(T, [y*rows,x*cols] ) + return T[0:(y*rows-padsize[0]), 0:(x*cols-padsize[1]) ] + +def quantise(C,Q): + """Quantise DCT coefficients using the quantisation matrix Q.""" + return C / repmat( Q, C.shape / Q.shape ) + +def dequantise(C,Q): + """Dequantise JPEG coefficients using the quantisation matrix Q.""" + return C * repmat( Q, C.shape / Q.shape ) + +def bdct(A,blksize=8): + """Blocked discrete cosine transform for JPEG compression.""" + dctm = bdctmtx(blksize) + (v,r,c) = im2vec(a,blksize) + return vec2im(dot(dctm,v),blksize,rows=r,cols=c) + +def ibdct(A,blksize=8): + """Inverse blocked discrete cosine transform""" + dctm = bdctmtx(blksize) + (v,r,c) = im2vec(a,blksize) + return vec2im(dot(transpose(dctm),v),blksize,rows=r,cols=c) + diff --git a/pyjpegobj/dct.py b/pyjpegobj/dct.py new file mode 100644 index 0000000..74aa372 --- /dev/null +++ b/pyjpegobj/dct.py @@ -0,0 +1,62 @@ +## $Id$ +## -*- coding: utf-8 -*- + +# jpeg.dct +# ======== + +from numpy import dot, linalg +import numpy + + +def auxcos(x, u): + return numpy.cos((numpy.pi / 8) * (x + 0.5) * u) + + +def cosmat(M=8, N=8): + C = numpy.array([[auxcos(x, u) for u in range(N)] + for x in range(M)]) / 2 + C[:, 0] = C[:, 0] / numpy.sqrt(2) + # C[0,:] = C[0,:] / numpy.sqrt(2) + return C + + +auxM = cosmat(8, 8) +invM = linalg.inv(auxM) +auxT = numpy.transpose(auxM) +invT = numpy.transpose(invM) + + +def dct2(g): + """ + Perform a 2D DCT transform on g, assuming that g is 8x8. + """ + assert (8, 8) == numpy.shape(g) + return dot(auxT, dot(g, auxM)) + + +def idct2(g): + """ + Perform a 2D inverse DCT transform on g, assuming that g is 8x8. + """ + assert (8, 8) == numpy.shape(g) + # return dot( invM, dot( g, invT ) ) + return dot(invT, dot(g, invM)) + + +def bdct(C, f=dct2): + """ + Make a blockwise (8x8 blocks) 2D DCT transform on the matrix C. + The optional second parameter f specifies the DCT transform function. + The height and width of C have to be divisible by 8. + """ + (M, N) = numpy.shape(C) + assert M % 8 == 0 + assert N % 8 == 0 + S = numpy.ndarray((M, N)) + for i in range(0, M, 8): + for j in range(0, N, 8): + S[i:(i + 8), j:(j + 8)] = f(C[i:(i + 8), j:(j + 8)]) + return S + + +def ibdct(C): return bdct(C, f=idct2) diff --git a/pyjpegobj/dct.pyc b/pyjpegobj/dct.pyc new file mode 100644 index 0000000..2ef5517 Binary files /dev/null and b/pyjpegobj/dct.pyc differ diff --git a/test_jpeg.py b/test_jpeg.py index 1600bd3..96b92f4 100644 --- a/test_jpeg.py +++ b/test_jpeg.py @@ -2,8 +2,8 @@ __author__ = 'chunk' import numpy as np import mjsteg -import jpegObj -from jpegObj import base +import pyjpegobj +from pyjpegobj import base from common import * @@ -26,7 +26,7 @@ def test_setblocks(): """ wholewise """ - imb = jpegObj.Jpeg("res/test4.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") block = imb.getCoefBlock(channel='Y', loc=(-1, 2)) print block @@ -37,17 +37,17 @@ def test_setblocks(): imb.Jwrite("res/test4.jpg") - ima = jpegObj.Jpeg("res/test3.jpg") - imb = jpegObj.Jpeg("res/test4.jpg") - jpegObj.diffblocks(ima, imb) + ima = pyjpegobj.Jpeg("res/test3.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") + pyjpegobj.diffblocks(ima, imb) def test_setblocks2(): """ wholewises """ - ima = jpegObj.Jpeg("res/test3.jpg") - imb = jpegObj.Jpeg("res/test4.jpg") + ima = pyjpegobj.Jpeg("res/test3.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") block = ima.getCoefBlock(channel='Y', loc=(-1, 2)) print block @@ -62,16 +62,16 @@ def test_setblocks2(): imb.Jwrite("res/test4.jpg") - ima = jpegObj.Jpeg("res/test3.jpg") - imb = jpegObj.Jpeg("res/test4.jpg") - jpegObj.diffblocks(ima, imb) + ima = pyjpegobj.Jpeg("res/test3.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") + pyjpegobj.diffblocks(ima, imb) def test_setblock(): """ blockwise """ - imb = jpegObj.Jpeg("res/test4.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") block = imb.getCoefBlock(channel='Y', loc=(0, 1)) print block @@ -82,11 +82,11 @@ def test_setblock(): blocks2 = imb.Jgetblock(1, 0, 0) block_to_show = np.frombuffer(blocks2, dtype=np.int16, count=-1, offset=0).reshape(8, 8) print block_to_show - jpegObj.diffblock(blocks1, block_to_show) + pyjpegobj.diffblock(blocks1, block_to_show) def test_split(): - imb = jpegObj.Jpeg("res/test3.jpg") + imb = pyjpegobj.Jpeg("res/test3.jpg") c = imb.getCoefMatrix(channel='Y') print type(c[0, 0]) d = c.ravel() @@ -133,7 +133,7 @@ def test_bitbyte(): def test_iter(): - imb = jpegObj.Jpeg("res/test4.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") blocks = imb.getCoefBlocks(channel='Y') cnt = 0 for x in np.nditer(blocks, op_flags=['readwrite']): @@ -168,12 +168,12 @@ if __name__ == '__main__': # test_bitbyte() - ima = jpegObj.Jpeg("res/test3.jpg", key=sample_key) - # imb = jpegObj.Jpeg("res/new.jpg",key=sample_key) - imc = jpegObj.Jpeg("res/steged.jpg", key=sample_key) + ima = pyjpegobj.Jpeg("res/test3.jpg", key=sample_key) + # imb = pyjpegobj.Jpeg("res/new.jpg",key=sample_key) + imc = pyjpegobj.Jpeg("res/steged.jpg", key=sample_key) print ima.Jgetcompdim(0) print ima.getkey(), imc.getkey() - print jpegObj.diffblocks(ima, imc) + print pyjpegobj.diffblocks(ima, imc) # c1 = ima.getCoefBlocks() # c2 = imb.getCoefBlocks() diff --git a/test_steg.py b/test_steg.py index 9beb4a5..88e8eda 100644 --- a/test_steg.py +++ b/test_steg.py @@ -6,7 +6,7 @@ import pylab as plt import mjpeg import mjsteg -import jpegObj +import pyjpegobj from msteg.steganography import LSB, F3, F4, F5 from common import * diff --git a/test_steganal.py b/test_steganal.py index 2fa1a03..9918bab 100644 --- a/test_steganal.py +++ b/test_steganal.py @@ -1,12 +1,7 @@ __author__ = 'chunk' import numpy as np -import pylab as P -import pylab as plt - -import mjpeg -import mjsteg -import jpegObj +from pyjpegobj import * from msteg.steganography import LSB, F3, F4, F5 from msteg.steganalysis import MPB @@ -33,10 +28,10 @@ if __name__ == '__main__': timer = Timer() timer.mark() - ima = jpegObj.Jpeg("res/test3.jpg", key=sample_key) + ima = Jpeg("res/test3.jpg", key=sample_key) timer.report() # 0.006490s - ciq = ima.coef_arrays[jpegObj.colorMap['Y']] + ciq = ima.coef_arrays[colorMap['Y']] timer.report() # 0.000019s mpbSteg = MPB.MPB() diff --git a/yaj.py b/yaj.py index 7a3d74c..14a7289 100644 --- a/yaj.py +++ b/yaj.py @@ -2,7 +2,7 @@ __author__ = 'chunk' import numpy as np import mjsteg -import jpegObj +import pyjpegobj from common import * timer = Timer() @@ -44,7 +44,7 @@ def test_setblocks(): """ wholewise """ - imb = jpegObj.Jpeg("res/test4.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") block = imb.getCoefBlock(channel='Y', loc=(-1, 2)) print block @@ -55,8 +55,8 @@ def test_setblocks(): imb.Jwrite("res/test4.jpg") - ima = jpegObj.Jpeg("res/test3.jpg") - imb = jpegObj.Jpeg("res/test4.jpg") + ima = pyjpegobj.Jpeg("res/test3.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") diffblocks(ima, imb) @@ -64,8 +64,8 @@ def test_setblocks2(): """ wholewises """ - ima = jpegObj.Jpeg("res/test3.jpg") - imb = jpegObj.Jpeg("res/test4.jpg") + ima = pyjpegobj.Jpeg("res/test3.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") block = ima.getCoefBlock(channel='Y', loc=(-1, 2)) print block @@ -80,8 +80,8 @@ def test_setblocks2(): imb.Jwrite("res/test4.jpg") - ima = jpegObj.Jpeg("res/test3.jpg") - imb = jpegObj.Jpeg("res/test4.jpg") + ima = pyjpegobj.Jpeg("res/test3.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") diffblocks(ima, imb) @@ -89,7 +89,7 @@ def test_setblock(): """ blockwise """ - imb = jpegObj.Jpeg("res/test4.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") block = imb.getCoefBlock(channel='Y', loc=(0, 1)) print block @@ -104,7 +104,7 @@ def test_setblock(): def test_split(): - imb = jpegObj.Jpeg("res/test3.jpg") + imb = pyjpegobj.Jpeg("res/test3.jpg") c = imb.getCoefMatrix(channel='Y') print type(c[0, 0]) d = c.ravel() @@ -149,7 +149,7 @@ def test_bitbyte(): print bytesraw def test_iter(): - imb = jpegObj.Jpeg("res/test4.jpg") + imb = pyjpegobj.Jpeg("res/test4.jpg") blocks = imb.getCoefBlocks(channel='Y') cnt = 0 for x in np.nditer(blocks, op_flags=['readwrite']): -- libgit2 0.21.2