32
Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 1 BLOCK TRUNCATION CODING 1 INTRODUCTION The problem of image compression needs to answer the following basic questions: (1) Can data in an image be represented in binary form efficiently? (2) Is the distortion introduced in the decompressed image minimal and imperceptible to the human eye? If both the questions can be answered in the affirmative, then block truncation coding can be effectively applied to compress the image. The data rate (bits per pixel) needs to be minimal and defines the bandwidth required to transmit image bits over the network. There is usually a trade-off between data rate and distortion produced. More compression (less data rate) means more distortion and vice-versa. In Block Truncation Coding distortion is introduced and hence this is a lossy compression method meaning loss of data is inevitable making the decompressed output distorted. Distortion of images can be accepted by the user for applications in which the visual representation of images is not meant for critical analysis (For example: medical images or satellite images). The BTC algorithm is a statistical compression method meaning the image is divided into a number of non-overlapping blocks and the compression is applied to each block which is adaptable to local image statistics. The disadvantage is that the borders of the blocks are often visible in the decoded output. BTC is thus known to introduce blocking artifacts. BTC has been explained in this document which is defined as a block-adaptive binary encoder scheme based on moment preserving quantization.

BTC images

Embed Size (px)

Citation preview

Page 1: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 1

BLOCK TRUNCATION CODING

1 INTRODUCTION

The problem of image compression needs to answer the following basic questions:

(1) Can data in an image be represented in binary form efficiently?

(2) Is the distortion introduced in the decompressed image minimal and

imperceptible to the human eye?

If both the questions can be answered in the affirmative, then block truncation

coding can be effectively applied to compress the image.

The data rate (bits per pixel) needs to be minimal and defines the bandwidth

required to transmit image bits over the network. There is usually a trade-off

between data rate and distortion produced. More compression (less data rate)

means more distortion and vice-versa.

In Block Truncation Coding distortion is introduced and hence this is a lossy

compression method meaning loss of data is inevitable making the decompressed

output distorted. Distortion of images can be accepted by the user for applications in

which the visual representation of images is not meant for critical analysis (For

example: medical images or satellite images).

The BTC algorithm is a statistical compression method meaning the image is

divided into a number of non-overlapping blocks and the compression is applied to

each block which is adaptable to local image statistics. The disadvantage is that the

borders of the blocks are often visible in the decoded output. BTC is thus known to

introduce blocking artifacts.

BTC has been explained in this document which is defined as a block-adaptive

binary encoder scheme based on moment preserving quantization.

Page 2: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 2

2 The Basics of BTC

The basic BTC algorithm is a lossy fixed length compression method that uses a Q-

level quantizer to quantize a local region of the image. The quantizer levels are

chosen such that a number of the moments of a local region in the image are

preserved in the quantized output. In its simplest form, the objective of BTC is to

preserve the sample mean and sample standard deviation of a grayscale image.

Additional constraints can be added to preserve higher-order moments. For this

reason BTC is a block adaptive moment preserving quantizer.

The first step of the algorithm is to divide the image into non-overlapping

rectangular regions. For the sake of simplicity we let the blocks be square regions of

size n x n where n is typically 4. For a two-level (1-bit) quantizer, the idea is to

select two luminance values to represent each pixel in the block. These values are

chosen such that the sample mean and standard deviation of the reconstructed

block are identical to those of the block. An n x n bit map is then used to determine

whether a pixel luminance value is above or below a certain threshold. In order to

illustrate how BTC works, we will let the sample mean of the block be the threshold;

a “1” would then indicate if an original pixel value is above this threshold and a “0” if

it is below.

By knowing the bit map for each block, the decompression/ reconstruction algorithm

knows whether a pixel is brighter or darker than the average. Thus, for each block

two gray-scale values, a and b, are needed to represent the two regions. These are

obtained from the sample mean and sample standard deviation of the block, and

they are stored together with the bit map.

An entire worked example is given below along with the algorithm to make the steps

of the BTC crystal-clear.

Page 3: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 3

3 BTC PROCEDURE

BTC is based on preserving the first and second statistical moments of the image.

BTC is divided into blocks. The block size is usually 3 x 3 or 4 x 4 pixels. Within

each block, a threshold is chosen, and the value at each pixel is coded 0 or 1

depending on whether it is above or below the threshold. BTC attempts to preserve

the mean and variance (first and second statistical moment) of each block.

Step 1: The image is partitioned into a set of non-overlapping blocks. Let X(m, n)

be the original image which is partitioned into blocks of pixels which is represented

as a matrix f(m, n).

Step 2: For each block, the mean, mean square and the variance are calculated.

a) The mean is calculated as 흁 = ퟏ풎풙풏

∑ ∑ 풇(풎,풏)푵 ퟏ풏 ퟎ

푵 ퟏ풎 ퟎ

b) The mean square value is calculated using the formula

풇ퟐ = ퟏ풎풙풏

∑ ∑ 풇ퟐ(풎,풏)푵 ퟏ풏 ퟎ

푵 ퟏ풎 ퟎ

c) The variance is computed using the formula

흈 = 풇ퟐ - 흁ퟐ

Step 3: A binary allocation matrix B (m, n) is constructed in such a way that

B (m, n) = ퟏ:풇(풎,풏) > 휇ퟎ:풇(풎,풏) ≤ 흁

Let q represent the number of pixels greater than mean. In other words q is the

number of ones in matrix B.

Page 4: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 4

Step 4: Two values a and b for each block is calculated using the formula

풂 = 흁 − 흈 풒풎 풒

, 풃 = 흁 + 흈 풎 풒풒

Here m is the number of pixels within each block.

Step 5: After calculating the values a and b, the block values are reconstructed as

풇 (m, n) = 풂:푩(풎,풏) = ퟎ풃:푩(풎,풏) = ퟏ

The biggest advantage offered by the BTC method is that it lends itself nicely to

parallel processing.

4 WORKED EXAMPLE

Apply BTC procedure to the block and obtain the reconstructed value for the

following image block.

푓(풎,풏) =

ퟔퟓ ퟕퟓ ퟖퟎ ퟕퟎퟕퟐ ퟕퟓ ퟖퟐ ퟔퟖퟖퟒ ퟕퟐ ퟔퟐ ퟔퟓퟔퟖ ퟔퟖ ퟕퟐ ퟖퟎ

Step 1: Computation of mean, mean square and variance of the block.

(a) Mean value is computed as µ.

µ = (65 + 75 + 80 + 70 + 72 + 75 + 82 + 68 + 84 + 72 + 62 + 65 + 68 + 68 + 72 + 80)

µ = 72.25

(b) Mean square value 푓 is calculated as

푓 = 1

16 (652 + 752 + 802 + 702 + 722 + 752 + 822 + 682 + 842 + 722 + 622 +

652 + 682 + 682 + 722 + 802 )

Page 5: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 5

푓 = 5261.25

c) The variance of the block is computed as 휎 = 푓 − 휇

휎 = 5261.25 – 72.25 x 72.25 = 41.1875 ⟹ 휎 = 6.4177

Step 2: Computation of binary allocation matrix B(m, n)

B (m, n) = ퟏ:풇(풎,풏) > 휇ퟎ:풇(풎,풏) ≤ 흁

Each element in the matrix f(m, n) is compared against the mean value. If

f(m, n) is greater than the mean value then 1 is assigned, otherwise 0 is

assigned.

푩(풎,풏) = ퟎ ퟏ ퟏ ퟎퟎ ퟏ ퟏ ퟎퟏ ퟎ ퟎ ퟎퟎ ퟎ ퟎ ퟏ

The number of ones in B(m, n) is 6. Hence q = 6.

Step 3: Computation of a and b values

풂 = 흁 − 흈 풒풎 풒

, 풃 = 흁 + 흈 풎 풒풒

Here m=16, µ = 72.25,q=6,σ=6.4177

∴ 퐚 = ퟔퟕ.ퟐퟕퟖ ≅ ퟔퟕ and b = 80.53 ≅ ퟖퟏ

Step 4: Reconstructed block is given as

풇 (m, n) = 풂:푩(풎,풏) = ퟎ풃:푩(풎,풏) = ퟏ

Page 6: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 6

풇 (m, n) =

ퟔퟕ ퟖퟏ ퟖퟏ ퟔퟕퟔퟕ ퟖퟏ ퟖퟏ ퟔퟕퟖퟏ ퟔퟕ ퟔퟕ ퟔퟕퟔퟕ ퟔퟕ ퟔퟕ ퟖퟏ

By comparing f and 푓 , we find that the error is inevitable.

5 JAVA CODING

Over the next few pages, we shall describe in vivid detail about the Java

programming for this algorithm. The coding has been kept straightforward and

explains each step and the images used for this project.

Basic Assumptions:

We have coded for grayscale jpg images. The algorithm can be extended to color

images (RGB) in jpg or png format, although the coding does not cover that.

The images that we have used are of academic interest found widely over the

internet. The images are not of any critical applications and this algorithm works

well for images that can use for web pages displaying informative content (not

critical) like wildlife photography or movies.

We have discussed all the program files. The codes have been reproduced here in

red colour and a suitable explanation of that particular code snippet follows that in

brown colour.

The driver file (containing main ()) is TestBTC.java

5.1 Code in TestBTC.java

import java.io.*; The above package needs to be imported for the “File” class that searches the file

of image on the disk. This shall be explained in detail a little while later.

Page 7: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 7

import javax.imageio.*; The above package needs to be imported for the “ImageIO” class that reads the file

as an image on the disk. This shall be explained in detail a little while later. import java.awt.image.*; The above package needs to be imported for the “BufferedImage” class. This shall

be explained in detail a little while later. import java.util.*;//for class Date The above package needs to be imported for the “Date” class. This shall be

explained in detail a little while later. public class TestBTC { public static void main(String []args){ long lStartTime=new Date().getTime(); The above long variable stores the current time in milliseconds, time which is the

beginning of the program. This is no way connected to the algorithm but a useful

statistic to calculate the speed of the program.

The above class is the driver class where the main control exists.

String inputfile="abc"; String inputpath="D:/BTC/" +inputfile +".jpg"; String outputpath="D:/BTC/"+inputfile +"_BTC.jpg";

The variable inputfile holds the name of the jpg grayscale image file.

The other two variables define the input and out file paths respectively.

D: /BTC/ signifies that the folder BTC is located on the D-drive and we are using

Windows platform to execute the program. These things change on other OS

platforms.

Page 8: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 8

short BTCinput [] [];

We have defined short variable that takes up 4 bytes in JAVA. We have declared a

2-D array whose every element shall carry 4 bytes in RAM. The reason is that the

pixel values are in the range [0,255] and we do not required integer arrays to store

such small values. Integer variables are 8 bytes long resulting in unnecessary

memory wastage. This decision was taken after it was observed that “short”

variables can be processed faster when the image size is huge and computation is

expensive.

BufferedImage image=null;

The class file BufferedImage was briefly touched upon in the beginning and this is

the right place to discuss its value. The image object is of BufferedImage class and

initialized as null. This actually is useful when Images stored in files on the

secondary disk are loaded into RAM for processing during run-time. The image

pixel values are stored in a buffer wherein it becomes easier and faster to process.

The images are scanned in raster format (meaning row-by-row accessing each

column, similar to matrix operations studied in a lower class or college). The images

accessed in this manner have minimum X and Y coordinates equal to zero each.

If the image has dimensions 4 X 7 meaning width=4 and height=7 (in pixels) then

the BufferedImage class stores the image in the following format.

Page 9: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 9

try { image = ImageIO.read (new File(inputpath)); }

catch (IOException e) { System.out.println("Input file not existing or possibly wrong path"); }

The above try-catch block is essential to catch the exception during run-time that

the specified path for the image may not contain the image itself. The

BufferedImage class that we discussed earlier shall work only if the ImageIO class

is used which ensures the file is read in the form of an image. So in essential we are

reading the input (jpg file) using file class and interpret it like an image containing

pixel values using ImageIO class which in turn makes it possible for the

BufferedImage class to buffer the image. The catch block reports the error saying

that the input file is non-existent or the path itself is wrong. We now begin the

encoding part of the algorithm.

GrayRGBRead pixelread=new GrayRGBRead(image);

The object pixelread for the class GrayRGBRead is created that takes the image

buffer as an input for its constructor. We shall discuss GrayRGBRead shortly.

BTCinput=pixelread.RasterRead ();

The BTCinput array stores the pixel luminance values when the pixelRead method

of the object is invoked and completed.

short [] OverHeadInfo; OverHeadInfo=pixelread.OverHeadInfo ();

The array OverHeadInfo shall be discussed later.

Page 10: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 10

BlocksCreate blocks = new BlocksCreate (BTCinput);

After reading the image we need to create square pixel blocks as per the algorithm

requirement. We shall discuss this in depth later.

byte [] [] [] BAMFinal;

This 3-D array shall be treated with care later. This array carries the binary

allocation matrix values for each block.

short [] [] MeanVarFinal;

This matrix carries the statistical values like mean and standard deviation for each

block.

The above two arrays are the outputs produced by the encoder that passes to the

decoder via a network channel.

Now we define the decoder-side variables and wrap up the main () method.

DecoderBTC decoder=new DecoderBTC (BAMFinal, MeanVarFinal, OverHeadInfo);

The decoder constructor requires the binary allocation matrix, statistical moments

and the overhead information. Overhead information is something that was used as

a part of the programming logic and is not actually an algorithm constraint.

short ImageOutput [][];

ImageOutput=decoder.DecodeOutputFinal ();

The above 2D-array is the decoder output that is used to write the image and store

on the secondary disk for display purpose.

WriteImage writeop = new WriteImage(ImageOutput[0].length,ImageOutput.length,ImageOutput,outputpath);

The class is meant for storing the output BTC generated image. More on this later.

Page 11: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 11

long lEndTime=new Date().getTime;

This variable stores the millisecond value when the program ends.

long difference=lEndTime-lStartTime;

This statement gives the amount of milliseconds to execute the program. This is

only for the sake of programming and knowing whether the algorithm works quickly

enough to be deemed a good option practically.

System.out.println ("Elapsed time in milliseconds: "+difference);

The execution time displayed on the terminal or command prompt.

5.2 Code in GrayRGBRead.java

The class GrayRGBRead reads the pixel luminance values in raster format.

CommonVar is another class which defines static final variable called “BlockSize”

which defines one dimension of the block. For Example, BlockSize=3 implies that

the BTC image blocks are of size 3x3. GrayRGBRead inherits this class because

BlockSize variable is essential for the logic as explained below.

public class GrayRGBRead extends CommonVar{

The above way of declaration indicates the inheritance concept explained in the

above paragraph.

private BufferedImage pixelRead;

The BufferedImage pixel reader that obtains the image pixels from TestBTC object.

private int imheight,imwidth;

The variables to store the dimensions of the image like height and width.

private byte flag=0;

This variable is important from the logic point-of-view to be explained later.

Page 12: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 12

private short addw=0,addh=0;

These variables are important from the logic point-of-view to be explained later.

public GrayRGBRead(BufferedImage pr){ pixelRead=pr; imheight=pixelRead.getHeight(); imwidth=pixelRead.getWidth();

}

The constructor obtains the input from TestBTC and the raster pixel array, image

dimension variables being initialized.

public short[][] RasterRead(){

This method has been defined to read the pixel values to be read from the buffer for

further processing and analysis. This method can be useful to read binary image

data as well.

Raster image_raster=pixelRead.getData();

Raster class defined by Java allows us to read data in raster format. getData()

method accomplishes this task. pixelRead is the BufferedImage object that was

defined earlier.

short [][] raster = new short[imheight][imwidth];

This array is defined to store the pixel values. It is important to note that image in

raster format is till in the JFIF format (format of JPG image standards) and this

needs to be converted into integers for our processing which is accomplished in the

next 4-5 lines of the code.

Page 13: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 13

int pixel[] =null; for(int j=0;j<imheight; j++)

for(int i=0;i<imwidth; i++) { pixel= image_raster.getPixel(i,j,pixel); raster[j][i]=(short)pixel[0];

}

The getPixel() method is extremely important from our perspective. This method is

essential to obtain RGB format values into ordinary integer values. raster[][] can

now be processed easily for any application that we need to work with.

short [][] addGraydata=null; int remainder=0;

We shall discuss the requirement of the above two variables below with an

example.

Assume BlockSize is 3.

If we want to make blocks of dimension NXN (N=3 in our case) then the width and

height need to be clearly divisible by N (i.e. remainder=0). If the image dimensions

are say, 600 X 480 then we have no issues but if the image dimensions are like

480X481 OR 362X600 OR 451X514 then we have a problem.

In the case of 480X481 we find the width to be divisible completely by 3 but the

height leaving a remainder of1 (or non-zero) when divided by 3. This poses a

problem to create blocks as some data would not be compressed. This definitely

needs to be rectified. Similarly in the cases of . 362X600 and 451X514 one or both

the dimensions are not divisible completely by the BlockSize of 3. This needs to be

identified by the next following lines of code.

Page 14: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 14

if(imheight % BlockSize !=0 && imwidth% BlockSize==0) flag=1; if(imwidth % BlockSize !=0 && imheight% BlockSize==0) flag=2; if(imwidth % BlockSize !=0 && imheight% BlockSize!=0) flag=3;

The modulus operator does our job fairly easily and for a particular image only one

of the above three conditions would be true. This is known by the flag variable

value.

switch(flag){ case 1:

remainder=imheight % BlockSize; if(remainder==1) addh=BlockSize-1; if(remainder==2) addh=BlockSize-2;

if(remainder==3) addh=BlockSize-3; addGraydata=Adjustment(raster,imheight+addh,imwidth,flag); return addGraydata;

We now define a switch-case block that deals with the three possible values of flag.

addh variable stores the remainder of the modulo operation when the height is not

exactly divisible by the BlockSize. Similarly we have defined addw variable for the

width.

Let us discuss case 1. Case 2 and case 3 are fairly simple and can be easily

understood. In case 1 above, we check the remainder after the modulo operation.

For BlockSize 3, we can have remainder 1 or 2, but for BlockSize 4 we can have a

remainder 3 as well. The code has been written to demonstrate the effects of 4X4

size blocks but that shall be dealt in the results section of the document. If

remainder=1 then we need to add 2 additional rows (height defines the row in

image as discussed in TestBTC.java section of 5.1) in the image containing zero

Page 15: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 15

values. Example If height=13, we need to add 2 to make it 15 (as 15 is exactly

divisible by zero).

After the flag variable value is determined, we now call the method “Adjustment“

which only adds the additional number of rows in the image matrix filled with zeros.

Similarly case 2 adds additional columns while case 3 adds both additional rows

and columns. Remember, we are not manipulating the original image but adding

data in the matrix that shall be processed by our code. Later on in the decoding

side, we shall drop these additional rows or columns or both.

addGraydata array variable returns the original image in addition to the additional

rows or columns or both for further processing as per the code requirement.

If all the three conditions fail (when column and row is exactly divisible by the

BlockSize then we return raster unchanged.

private short [][]Adjustment(short [][]GrayAdjust,int height, int width, byte indicator)

This method does the job of appending additional rows or columns filled with zeros

for making ‘perfect’ blocks of BlockSize NXN.

short [][] addGraydata=new short [height][width];

for(int j=0;j<imheight; j++) for(int i=0;i<imwidth; i++) addGraydata[j][i]=GrayAdjust[j][i];

The above code elucidates the simple job of copying the original image content as

it is.

Now we use another switch-case block to append additional dimensions. We

discuss case 1 only while case 2 and case 3 are mere extensions of the same logic.

Page 16: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 16

switch(indicator){ case 1:

for(int j=imheight; j<height; j++) for(int i=0;i<imwidth; i++) addGraydata[j][i]=0; break;

The above code adds the additional zero values for appended rows.

public short []OverHeadInfo(){

short Overhead[]=new short [3]; Overhead[0]=(short)imwidth; Overhead[1]=addw;

Overhead[2]=addh; System.out.println(Overhead[0] + " "+Overhead[1]+" "+Overhead[2]);

return Overhead; }

The above method captures essential details like the number of additional rows

and/or columns that have been appended to make perfect blocks. Remember this

information is useful to write the output image. If we write the output image with

extra rows and/or columns we shall get a ‘bloated’ image. This is not expected of

the algorithm. The image dimensions should not change for the decompressed

image output. We have used Overhead [0] to give one dimension of the image to

the decoder. This information is vital to recover the other dimension as we shall

discuss later. We have used the word ‘Overhead’ because it is the additional

Page 17: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 17

information required by the decoder to know the actual number of columns and/or

rows added so that they can be trimmed finally.

We thus finish this java file which essentially created an array with additional rows

and/or columns. This array shall now be used to create blocks which define the vital

encoding part of the BTC algorithm.

5.3 BlocksCreate.java

This java class also inherits CommonVar class because BlockSize variable is

critical to this encoding procedure.

public class BlocksCreate extends CommonVar {

short BTCinput[][]; short BlockData[][][]; float BlockMeanVar[][]; short StatisticsFinal[][];

int NumBlocks; byte BAM[][][];

BTCinput array contains the pixels of the image. BlockData array shall be created in

this class for every block of size NXN. We have defined a 3-D array that shall be

explained below. BlockMeanVar array calculates mean and standard deviation for

each block. StatisticsFinal is the array that stores the round-off values for mean and

standard deviation. Float variables occupy 4 bytes each as far as Java is concerned

and short variables occupy 2 bytes only. So if we send across float variables across

the network to the decoder then the purpose of compression itself is defeated.

For Block 3X3:

Total pixels = 9

Each pixel = 2 bytes.

So we require 18 bytes for every block in the image. To achieve compression we

are using binary allocation matrix (byte BAM[][][]) that allots 1 byte to each pixel and

Page 18: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 18

we also have mean and standard deviation with 2 bytes each (StatisticsFinal

variable). Thus we use only 1*9 +2+2 = 13 bytes per block. If we had used float

variable for this purpose we would have had 1*9 +4+4=17 bytes per block which is

approximately equal to the original block memory. This would have defeated the

purpose of utilizing the network bandwidth optimally.

Thus for 9 pixels we use up 13 bytes of memory which means we have 13/9 =

1.44bpp (bytes per pixel) of memory to achieve effective compression.

The amount of compression achieved per pixel is = .

=1.38

The variable NumBlocks denotes the number of blocks in the image. Say the image

dimension is 40X45 with the BlockSize being 3X3. Then 40X45 becomes 42X45

with the addition of two more columns and NumBlocks = = 14X15 = 210

BAM is a 3D array that is actually the binary allocation matrix data for each

block.

public BlocksCreate (short BTCinput [][]){ this.BTCinput =BTCinput; MakeTheBlocks();

}

The constructor accepts the matrix data of pixels and the method is called.

private void MakeTheBlocks(){

int blockwidth=BTCinput [0].length/BlockSize; int blockheight=BTCinput.length/BlockSize; NumBlocks=blockwidth*blockheight;

Page 19: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 19

This method calculates the NumBlocks firstly as elucidated with the example

discussed in the earlier page. BTCinput[0].length returns the columns of the matrix

while BTCinput.length returns the rows of the matrix.

BlockData=new short[NumBlocks][BlockSize][BlockSize]; BAM=new byte[NumBlocks][BlockSize][BlockSize];

The first 3D array shall hold the data for each block while the second 3D array shall

consist of the binary allocation matrix for each block.

for (int k=0,ii=0, jj=0;k<NumBlocks; k++, jj+=BlockSize){

if (jj>=BTCinput[0].length) { jj = 0;

ii+=BlockSize; } for (int i=0;i<BlockSize;i++){ for(int j=0;j<BlockSize;j++) {

if( (ii+i) >= BTCinput.length) break; if( (jj+j)>=BTCinput[0].length)break; BlockData[k][i][j]=BTCinput[ii+i][j+jj]; }

} }

10 10 10 12 21

12 23 12 14 17

15 16 11 14 17

13 12 15 14 14

Page 20: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 20

Consider the above 5X4 hypothetical image matrix as an example to explain the for-

loop. This is a vital part of the code which can be better explained by the example.

Since BlockSize=3X3, we find that both height and width are not exactly divisible by

3, we append additional rows and columns to make the dimensions exactly divisible

by 3.

The revised matrix is shown above whose dimensions are 6X6. Thus the number of

blocks is NumBlocks =

= 2 X 2 = 4

We have the four blocks with four different colours as shown above.

Now we again go back to the nested for loop and understand it using the above 6X6

matrix image as an example.

For block 0, we need all the brown-coloured pixel values.

For block 1, we need all the dark blue-coloured pixel values.

For block 2, we need all the red-coloured pixel values.

For block 3, we need all the blue-coloured pixel values.

if (jj>=BTCinput[0].length) is added to ensure that we do not exceed array

dimensions column-wise. It also ensures that we move down to the next possible

block. “if ( (ii+i) >= BTCinput.length) break” ensures that we do not exceeded array

dimensions row-wise.

10 10 10 12 21 0

12 23 12 14 17 0

15 16 11 14 17 0

13 12 15 14 14 0

0 0 0 0 0 0

0 0 0 0 0 0

Page 21: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 21

MakeStatistics();

We call the method to calculate mean and standard deviation for each block.

private void MakeStatistics(){ BlockMeanVar = new float [NumBlocks][2];

StatisticsFinal = new short [NumBlocks][2];

This method begins with the definition of two variables. BlockMeanVar calculates

the statistics for each block with the 0th column containing mean and 1st column

containing standard deviation. StatisticsFinal stores the statistics for each block

after rounding-off the values. This rounding off essentially contributes to lossy

compression.

for(int k=0;k<NumBlocks;k++) { BlockMeanVar[k][0]=BlockMeanVar[k][1]=0.0f; StatisticsFinal[k][0]=StatisticsFinal[k][1]=0;

for(short i=0;i<BlockSize;i++){ for(short j=0;j<BlockSize;j++) { BlockMeanVar[k][0]+=BlockData[k][i][j];

BlockMeanVar[k][1]+=BlockData[k][i][j]*BlockData[k][i][j]; } }

BlockMeanVar[k][0]/=(float)BlockSize*BlockSize; BlockMeanVar[k][1]/=(float)BlockSize*BlockSize; BlockMeanVar[k][1]-=BlockMeanVar[k][0]*BlockMeanVar[k][0]; BlockMeanVar[k][1]=(float)Math.sqrt(BlockMeanVar[k][1]);

Page 22: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 22

StatisticsFinal[k][0]=(short)Math.round(BlockMeanVar[k][0]); StatisticsFinal[k][1]=(short)Math.round(BlockMeanVar[k][1]); }

For each block we initialize both the mean and standard deviation array cells to

zero. Then simple steps to calculate mean and standard deviation is included

followed by the rounding-off to complete the for loop.

MakeBAM ();

We now call the Binary allocation matrix creation method to create binary allocation

matrix from each block.

private void MakeBAM(){ for(int k=0;k<NumBlocks;k++)

{ for(int i=0;i<BlockSize;i++){ for(int j=0;j<BlockSize;j++) { if(BlockData[k][i][j]>StatisticsFinal[k][0])

BAM[k][i][j]=1; else BAM[k][i][j]=0; }

} } BlockData=null; }

In this method we simply compare the pixel value in each block with the mean. If the

pixel value is more than the mean the pixel value is considered as 1 in the binary

allocation matrix else it is considered zero.

Page 23: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 23

BlockData=null is only meant for garbage collection as it is no longer required.

public short [][]MeanVarFinal()

{ return StatisticsFinal; }

public byte [][][]BAMFinal(){ return BAM; }

The above two methods allow the main class to access the encoded data because

the remaining methods are encapsulated by being kept private. Binary Allocation

matrix and Statistics block are transferred via the network in the real-world scenario.

We have not written any socket programming logic to highlight the process of

sending data across the network.

5.4 DecoderBTC.java

public class DecoderBTC extends CommonVar{ private byte BAM[][][]; private short [][]MeanVar; private short DecoderOutput[][][];

private short OverHeadInfo[]; private short DecoderFinal[][];

The class again inherits the BlockSize variable. BAM array is the binary allocation

matrix received from the encoder. MeanVar stands for the statistics for each block.

OverHeadInfo is extremely important information required to exactly determine the

original dimensions of the input image because in the encoder additional rows

and/or columns may have been appended. DecoderOutput is the reconstructed

Page 24: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 24

output for each image block while DecoderFinal is the final image constructed after

piecing together all the blocks.

public DecoderBTC(byte [][][]BAMFinal, short [][]MeanVarFinal,short OverHeadInfo[]){ this.OverHeadInfo=OverHeadInfo; BAM=BAMFinal;

MeanVar=MeanVarFinal; DecodeProcess(); }

The constructor ends with the method being called that executes the decoding

operation.

private void DecodeProcess(){ DecoderOutput=new short[BAM.length][BAM[0].length][BAM[0].length];

The DecoderOutput array is of size say, [2048][3][3] where 2048 is the number of

blocks. The remaining two dimensions stand for the block size.

for(int k=0;k<BAM.length;k++){ float a=0,b=0; float calc=0; int c=0;

We calculate values a and b for each block. ‘calc’ and c are supporting variables for

calculation.

for(int i=0;i<BAM[0].length;i++){

for(int j=0;j<BAM[0].length;j++) { if (BAM[k][i][j]==1) c++; }

Page 25: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 25

}

Variable c keeps count of the number of ones in the binary allocation matrix for

each block.

if(c==BAM[0].length*BAM[0].length || c==0) { a=b=MeanVar[k][0];

} else

It is quite possible that all pixel values in binary allocation matrix for a block are

zeros. In that case c remains zero. Then we need to set a nd b both to the mean for

that block. It follows that c being zero results in division by zero as discussed in the

else block.

{

calc=(float)Math.sqrt(c/(BAM[0].length*BAM[0].length-c)); if(calc==0.0) { a=b=MeanVar[k][0];

} else { a = MeanVar[k][0] - MeanVar[k][1]*calc;

b = MeanVar[k][0] + MeanVar[k][1]/calc; } }

calc variable results in the calculations that involve square root. Due to values

closer to zero in certain cases, calc can be zero resulting in “divide-by-zero”

scenario. In that case a and b shall equal mean value else they get the values as

per the formula.

Page 26: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 26

for(int i=0;i<BAM[0].length;i++){ for(int j=0;j<BAM[0].length;j++) {

if(BAM[k][i][j]==1) DecoderOutput[k][i][j]=(short)Math.round(b); if(BAM[k][i][j]==0)

DecoderOutput[k][i][j]=(short)Math.round(a); if(DecoderOutput[k][i][j]<0) DecoderOutput[k][i][j]=0; }

} }

The nested for-loop calculates block-wise values for pixel using a, b and the binary

allocation matrix. The round() method in Math class rounds up the values due to

floating calculations involved.

int GotHeight=BAM.length*BlockSize*BlockSize/(OverHeadInfo[0]+OverHeadInfo[1]);

GotHeight-=OverHeadInfo [2]; int GotWidth=OverHeadInfo[0];

DecoderFinal=new short[GotHeight][GotWidth];

OverHeadInfo array gives us the actual width of the image, additional width and

height added to make a perfect block. GotWidth and GotHeight need to re-calculate

the original image dimensions using this additional information. GotWidth holds the

actual image width. GotHeight needs to be calculated using this information plus

other overhead details.

int ii=0,flag=0; for(int i=0,j=0;i<GotHeight;i+=BlockSize,j+= (GotWidth+OverHeadInfo[1]) /BlockSize){

Page 27: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 27

Here i variable takes care of the rows while j increments as per the columns.

Flag indicates that the numbers of rows have exceeded and when the value is 1,

the process stops.

for(int k=0,jj=0,h=0; k<GotWidth; k++,h++) { if(h==BlockSize ) {

h=0; jj++; }

if ((i+ii) >= GotHeight){ flag=1; break; }

DecoderFinal[i+ii][k]=DecoderOutput[j+jj][ii][h]; } }

}

DecoderFinal gets the final image output to be written to a file at the decoder side.

public short [][] DecodeOutputFinal(){

return DecoderFinal; }

DecoderFinal is the final output that can be accessed using this method by the main

class.

Page 28: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 28

5.5 WriteImage.java

This is the final part of the algorithm wherein the final output image is written. Here

again inheritance is used for the BlockSize variable.

public class WriteImage extends CommonVar{ public WriteImage(int width,int height,short Output[][],String outputpath){

WriteImage constructor requires the decoded output array, dimensions and the

outputpath on the disk.

BufferedImage new_img=new BufferedImage (width, height, BufferedImage.TYPE_INT_RGB);

Graphics gr=new_img.createGraphics();

gr.drawImage(new_img,0,0,null); gr.dispose();

Here we again use the BufferedImage concept. TYPE_INT_RGB is an in-built java

feature to write images in RGB format. It is interesting to note that our input was a

grayscale image and the output also needs to be of the same type. But

unfortunately we cannot do so directly. The grayscale output array that we have

needs to be written into an RGB image and this image then is converted to

grayscale image. More on this later.

The graphics class is required because image writing is a part of this class. It is like

creating an empty canvas for the painting to happen later. Graphics provides the

canvas on which we write (or draw) the image.

int row,col; for(row=0;row<height;row++){ for(col=0;col<width;col++){ int alpha=Output[row][col] << 16 | Output[row][col] << 8 | Output[row][col];

new_img.setRGB(col,row,alpha);

} }

Page 29: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 29

In the nested for-loop the variable alpha concatenates the same value from the 16

to the 23 rd position, 8th to 15th position and 0 to 7th position. This is because the

RGB has got 24 bits per pixel while our output array has outputs in the range 0-255

for which 8-bits is sufficient. Remember we need to write in RGB format first and

RGB to grayscale occurs later in the last part of this class.

The alpha value is set as RGB (OR JFIF format of jpg standard) by the in-built

setRGB method that requires the coordinates and the pixel luminance value as

parameters.

try {

ImageIO.write (new_img,"jpg",new File(outputpath)); } catch (IOException e){ System.out.println ("Path non-existent for output image"); }

ImageIO class writes the image buffer into the file defined by the output path. “jpg”

indicates that the file needs to be stored in a format specified by JPEG standards.

The try-catch block is included to catch the exception when the output path is

incorrect.

//rgb to gray conversion BufferedImage image=null; try{

image=ImageIO.read(new File(outputpath)); }catch(IOException e){ System.out.println("Path non-existent for output image"); }

This is the final part of the program. The RGB image needs to be converted to

grayscale. We use the same outputpath to read the file.

new_img=new BufferedImage (width, height, BufferedImage.TYPE_BYTE_GRAY);

Page 30: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 30

gr=new_img.createGraphics(); gr.drawImage (image, 0, 0, null); gr.dispose ();

try { ImageIO.write(new_img,"jpg", new File(outputpath)); }catch(IOException e){

System.out.println("Path non-existent for output image"); } //rgb to gray ends here }

TYPE_BYTE_GRAY is the only new feature that is of note in the above snippet.

This means that the output file will be stored as a grayscale image.

Now we discuss the test results in the next section.

6 RESULTS

We use a popular statistics called UIQI to find out the quality of the output using

various block sizes.

6.1 What is UIQI?

6.2 Results

We have tested 12 images as depicted in the following table. The UIQI values along

with the execution time in milliseconds have been given below. The computer is a

home desktop with AMD Athlon II X2 240 processor (2.81 GHz) and 896 MB RAM.

Page 31: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 31

Images UIQI Execution time in milliseconds

Block

size =

2 X 2

Block

size =

3 X 3

Block

size =

4 X 4

Block

size =

2 X 2

Block

size =

3 X 3

Block

size =

4 X 4

CanadianFlag 0.3294 0.2164 0.1742 359 312 203

deer 0.4042 0.2414 0.2422 172 156 141

flower 0.3706 0.2364 0.2251 656 453 422

hegde 0.3599 0.2311 0.2233 5797 3109 2563

icecream 0.3644 0.2207 0.2183 219 172 156

lavender 0.3541 0.2359 0.2358 610 406 359

lenna 0.2790 0.1936 0.1958 188 141 125

lion 0.3988 0.2367 0.2375 406 328 219

monarch 0.2608 0.1590 0.1283 94 94 94

myna 0.3742 0.2435 0.2326 156 157 109

tajmahal 0.3926 0.2338 0.2416 406 297 203

Mandril 0.3327 0.1533 0.1643 203 172 156

It is also interesting to note that the file sizes have changed slightly (see table on

the next page). They have reduced in some cases slightly but also got heavier in

other cases. It is interesting to see that we can also use this image compression on

images in offline mode to save them on disk to reduce disk space. The compression

can be carried on official documents to reduce disk space without sacrificing quality.

But such documents shall have to be experimented rigorously to confirm the

feasibility of the method.

The increase in block size decreases the UIQI and the execution time. Lesser the

UIQI, poorer is the image quality. Faster execution time can be achieved using

Page 32: BTC images

Block Truncation Coding for Grayscale Image Compression Prepared by – G.R.Hegde (200815805) Page 32

larger block sizes but at the cost of image quality. There is a clear trade-off between

image quality achieved and the execution time.

The table below depicts the file sizes on disk for the same images with all three

possible block sizes. The increase/decrease in disk space (percentage) is also

tabulated.