Você está na página 1de 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.


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

2 The Basi cs 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.


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 =
1
mxn
(m,n)
N-1
n=
N-1
m=

b) The mean square value is calculated using the formula

2
=
1
mxn

2
(m,n)
N-1
n=
N-1
m=

c) The variance is computed using the formula
o
2
=
2
-
2

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

Let q represent the number of pixels greater than mean. In other words q is the
number of ones in matrix B.


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
a = o
_
q
m-q
, h = + o
_
m-q
q

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) =_
a:B(m,n) =
h:B(m,n) = 1

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.
(m,n) = _
5 75 8 7
72 75 82 8
84 72 2 5
8 8 72 8
_
Step 1: Computation of mean, mean square and variance of the block.
(a) Mean value is computed as .
=
1
16
(65+75+80+70+72+75+82+68+84+72+62+65+68+68+ 72+80)
= 72.25
(b) Mean square value
2
is calculated as

2
=
1
16
(65
2
+ 75
2
+ 80
2
+ 70
2
+ 72
2
+ 75
2
+ 82
2
+ 68
2
+ 84
2
+ 72
2
+ 62
2
+
65
2
+ 68
2
+ 68
2
+ 72
2
+ 80
2
)
Block Truncation Coding for Grayscale Image Compression
Prepared by G.R.Hegde (200815805) Page 5

2
=5261.25
c) The variance of the block is computed as o
2
=
2
p
2

o
2
=5261.25 72.25 x 72.25 =41.1875 o =6.4177
Step 2: Computation of binary allocation matrix B(m, n)
B (m, n) =_
1:(m,n) > p
:(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.
B(m,n) = _
1 1
1 1
1
1
_
The number of ones in B(m, n) is 6. Hence q =6.
Step 3: Computation of a and b values
a = o
_
q
m-q
, h = + o
_
m-q
q

Here m=16, = 72.25,q=6,=6.4177
a =7.278 7 and b = 80.53 81
Step 4: Reconstructed block is given as

`
(m, n) =_
a:B(m,n) =
h:B(m,n) = 1


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


`
(m, n) =
7 81 81 7
7 81 81 7
81 7 7 7
7 7 7 81

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 J ava
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.


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.

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 J AVA. 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.


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.



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.

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.

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 J ava 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 J FIF format (format of J PG 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.





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.

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
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.

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
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 J ava 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
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 =
2 bytcs
1.44 bytcs
=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 =
42X45
3X3
=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;

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

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 =
6 X 6
3 X 3
=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
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 0
th
column containing mean and 1
st
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]);
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.

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
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++;
}
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.

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){

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.




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);
} }
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, 8
th
to 15
th
position and 0 to 7
th
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 J FIF 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 J PEG 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);
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.



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
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.

Você também pode gostar