The Implementation
To illustrate this model, I wrote an application using Python, the Python Imaging Library (PIL) (www.pythonware.com/products/pil/), and the numarray package for Python (www.stsci.edu/resources/software_hardware/numarray). PIL adds image-processing capabilities to Python and processes a range of image formats. numarray lets Python efficiently manipulate large numeric arrays, similar to Matlab, IDL, or Octave.
There is a common convention in processing binary (black-and-white) images: If any black pixel in the image is denoted by 1 and any white pixel is 0, you can "transform" any binary black-and-white image into a binary matrix. This is the approach used in my application.
In the embedding process, you select 858 squares from a larger matrix (a share in the model) and write back 9x9 squares into some other matrix (normally a bit-plane); see Listings One and Two, respectively. The algorithm to calculate the complexity of a region in a bit-plane can be implemented as in Listing Three.
def square(matrix, n, d): # returns a square slice of dim d from a matrix # n - square number maxX = len(matrix[0]) maxY = len(matrix[:,0]) xsquares = maxX/d ysquares = maxY/d dy, dx = divmod(n, xsquares) xslc = dx * d yslc = dy * d return matrix[xslc:xslc+d, yslc:yslc+d]
def writesquare(matrix, square, n): # writes a square of dim d into a bigger matrix # n - square number # returns the matrix with the overwritten sqaure maxX = len(matrix[0]) maxY = len(matrix[:,0]) sqX = len(square[0]) sqY = len(square[:,0]) xsquares = maxX/sqX ysquares = maxY/sqY dy, dx = divmod(n, xsquares) xslc = dx * sqX yslc = dy * sqY matrix[xslc:xslc+sqX, yslc:yslc+sqY] = square return matrix
def complexity(matrix): maxX = len(matrix[0]) maxY = len(matrix[:,0]) norm = maxX*maxY cmp = 0. first = matrix[0][0] for x in range(maxX): for y in range(maxY): if first != matrix[x][y]: cmp += 1 first = matrix[x][y] if cmp != 0: cmp /= norm return cmp
To ensure the "blind" character of the embedding scheme, you have to provide additional information (like sequence number and the flag for the conjugated areas) to any 8x8 square read from a share. The additional information is written back into the vessel, together with the 8x8 square of embedding data. The required space is obtained by bordering the square with one row and one column. Though generally defined, the functions in Listings Four(a) and (b) can perform the bordering as well.
(a) def insert_line(where, line, matrix): maxX = len(matrix[0]) maxY = len(matrix[:,0])+1 if len(line) != maxX: print "[ERR] Unable to append line." sys.exit(1) getback = zeros((maxY, maxX)) for y in range(maxY): if y == where: getback[y] = line else: if y < where: getback[y] = matrix[y] else: getback[y] = matrix[y-1] return getback (b) def insert_column(where, column, matrix): tmatrix = transpose(matrix) tcolumn = transpose(column) tmpback = insert_line(where, tcolumn[0], tmatrix) return transpose(tmpback)
The program also provides other tools for tasks such as generating a random binary matrix or checkerboard pattern, initializing log files, defining some standard directories, and the like. All these are implemented in a utils package.
The application has compression, embedding, and extraction modules. All components work on 8-bit gray-level or 24-bit true-color images. I focus on the 8-bit images because the processing of the true-color images only involves this extension: PIL lets you switch between different pixel representations of an image using the convert function. In terms of converted images, there are only two accepted formats: L (8-bit grayscale) and/or RGB (24-bit true color). This lets you convert input images in RGB format like this:
try: image = __original_image.convert("RGB") except IOError: print "Cannot convert image: " + image_name
After a successful conversion, the image object is in RGB format. For such objects, PIL offers a method that returns a tuple containing the individual image components ("bands" in the PIL documentation). This means you can say:
red, green, blue = image.split()
The red, green, blue objects this time are 8-bit grayscale images. All three modules of the application are implemented in a separate class called image.
Compression, which is based on BPCS, only determines image regions with a complexity above a determined threshold. These regions are overwritten with white color. The embedding module uses the modified BPCS scheme. The module performs the bordering of the 8x8 initial square, correspondingly inserting the sequence numbers and the "conjugated flag" if needed (see Listing Six, available at www.ddj.com/code/).
The function starts with a given sequence number and returns the last sequence number it used. This is because of the embedding scenario itself: During the process, you have to move from one bit-plane to another, and, in the case of true-color images, from one color component to another. So the function has to "know" what the current sequence number is and return the sequence number it finished with for the next iteration.
The extraction module checks the complexity of any region in the image and searches for embedded data. The application also tries to determine if the complete chain of sequence numbers was extracted; if not, it reports the missing squares.
As in Table 1, the only 2x2 matrices that should exist in a share are the diagonal ones. The extraction module also uses an error-detecting mechanism and informs you if some region of the image was altereda "wrong" 2x2 matrix was found. As the embedding function, the extraction function starts with an initial sequence number and returns the next sequence number when finished (see Listing Seven, available at www.ddj.com/code/).