This is the description of my program - how to use it and how it works. Keep in mind that it works on both color and grayscale bitmaps. To use it, you need to install the numpy and Pillow libraries, and you need to run it on Python 3.6.
The program, jpeg_compressor.py, is a python program that runs with command line arguments. If you don’t want to deal with command line arguments, you can just pass the --interactive argument to have the program prompt you. You can run jpeg_compressor.py -h for all the arguments. The most important one is --action {compress,decompress,both}
If your action is "compress" or “both,” you need to have these options:
--originalfile ORIGINALFILE
Filename for compressed file(either for storing the
compressed file or reading it.
--compressedfile COMPRESSEDFILE
Filename for compressed file(either for storing the
compressed file or reading it.
--blocksize BLOCKSIZE
Block size for image (for compression)
--qualityfactor QUALITYFACTOR
Quality Factor (generally 30,40, 50)
--compressionmethod {gzip,zlib,bzip,xz}
Compression Method
If your action is decompress, you only need to supply the "compressedfile" and “compressionmethod” arguments. For all actions, you can also supply this argument.
--verbose Prints Messages
If you want the program to annotate what it’s doing, plus tell you the compression ratios. For example:
python jpeg_compressor.py --action both --verbose --originalfile Kodak12gray.bmp --compressedfile file.gz --blocksize 8 --qualityfactor 50 --compressionmethod gzip
This command will first compress "Kodak12gray.bmp" into file.gz, with a blocksize of 8, quality factor of 50, and compress with gzip. It will then uncompress file.gz with gzip, and display the image. Because --verbose is turned on, the program will also print out what it’s doing.
An example of a decompress call to the above image:
python jpeg_compressor.py --action decompress --verbose --compressedfile file.gz --compressionmethod gzip
Notice how for the decompression, you just have to specify the filename and the compression method, since information like the block size is encoded in the image.
The code is fairly heavily commented, so I will just give a brief overview of how my program works. The main data structures used are the numpy matrices, and the PIL image format, which is used for reading and writing images.
To compress, the program first reads in the image as a numpy matrix, and then decides whether to send it to the color compressor or the grayscale compressor. These are the steps the compressor takes:
-
Read the image as a matrix
-
Crop the image to the block size
-
Level the matrix by subtracting 128
-
On each block of the matrix:
-
Perform the DCT (generate DCT matrix, do dct_matrix * block * transform_dct_matrix)
-
Quantize it (using the quantization matrix from the standard, modified with the quality factor).
-
Get all of the items in the zig-zag order and add them to a list
-
Add the DC component to a list of DC component
-
-
Add the DC components in front of the zig_zag list
-
Encode the height and width of the image into two bytes, using the formula:
high_byte = n/(block_size * 255)
low_byte = (n%(block_size * 255))/block_size
-
Add the width, height,quality factor, and block size to the zig zag list, so that it is encoded into the image (so the user doesn’t have to know these things).
-
Add a certain number m to each item in the list, so all of the bytes are above 0. Then, add m.
-
Compress the list of bytes using the lossless compression given by the user (gzip, bzip, zlib).
-
Save the compressed version as a file, and report statistics.
If the image is color, steps 2-4 are done on all 3 channels, and added to one long list of zags.
To view the compressed image:
-
Uncompress the file using the compression method given by the user.
-
Get the m, and subtract it from each item.
-
Get all of the relevant metadata (blocksize, quality factor, height, width), and use the height and width to find out if the image is color or b&w
-
Separate the DC components from the AC components
-
Create a new matrix that is height x width (if it is color, make height x width x 3)
-
For each section of the list that is block_size^2:
-
Restore the original shape (undoing the zig-zag algorithm and adding the DC component for that block)
-
Multiply by the quantization matrix
-
Undo the DCT (dctmatrix transform * m * dctmatrix)
-
Add it to the appropriate part of the new matrix
-
-
Re-level the matrix by adding 128
-
Display the image as a matrix
- If the image is a color image, it needs to be saturated (anything less than 0 = 0, anything greater than 255 = 255), and then turn it into an uint8 matrix
-
Display statistics (how long it took).
Note that for step 6, if the image is color, it will fill the blocks into all 3 channels.
One of the benefits of my style with lots of functions is that it is very easy to plug and change certain parts. For example, to add a new lossless compression, you just have to define the compress/uncompress functions, add it to the dictionary of compression methods, and add it as a command line argument. I added xz at the last minute due to a friend’s advice.
Some things I wish I did better:
-
Be able to encode the DC differences as opposed to just the DC coefficients - when I did it, I just got a max-min greater than 256, so I couldn’t encode it as a byte.
-
Avoid having to add that ‘m’ and subtract it later.
-
Somehow encode the compression technique so that the user does not have to enter it. The problem with this is that the bytes become compressed, so I wouldn’t know what to decompress it with to get the data.
>python jpeg_compressor.py --action both --verbose --originalfile Kodak12.bmp --compressedfile file.gz --blocksize 8 --qualityfactor 50 --compressionmethod gzip
Starting program with these arguments:
Namespace(action='both', blocksize=8, compressedfile='file.gz', compressionmethod='gzip', interactive=False, originalfile='Kodak12.bmp', qualityfactor=50, verbose=True)
Image is color image.
Opened color image Kodak12.bmp for compression, split into 3 channels
Leveled each block by subtracting 128
Performed DCT on each block of the image, and zig-zagged the blocks into a list
Encoded the width, height, block size, and compression method
Compressed Kodak12.bmp to file.gz using lossless compression method gzip
Block size 8 and quality factor 50
Took 4.33 seconds
Original file size: 1179704 bytes
New file size: 110256 bytes
Compression Ratio: 10.7
Decompressed file.gz using lossless decompression method gzip
This is a color photo
Restored each block by un-zigzagging, multiplying by the quantization matrix, and undoing the DCT
Re-leveled image by adding 128 to each pixel
Displaying Image (check your task bar)
Time taken: 3.76 seconds
The following is a result of my compression algorithm on the test images. For each one, I include the filename, original size (in bytes), compression ratio achieved with Pillow’s JPEG converter (with quality = 95 for PSNR 50, q=75 for 40, and q=35 for 30), and the compression ratio received with the blocksize and lossless compression technique. The block sizes I used were 8 and 16, and the lossless compressions I used were gzip, bzip, zlib, and xz.
Filename | Og. Size (bytes) | JPEG Compression ratio | Ratio (8/gzip) | 16/gzip | 8/bzip | 16/bzip | 8/zlib | 16/zlib | 8/xz | 16/xz |
Kodak08.bmp | 1179704 | 5.089 | 4.945 | 4.992 | 5.983 | 6.406 | 4.787 | 4.729 | 8.492 | 9.039 |
Kodak08gray.bmp | 394296 | 1.865 | 4.863 | 4.921 | 5.422 | 5.5 | 4.72 | 4.669 | 5.617 | 5.508 |
Kodak09.bmp | 1179704 | 8.873 | 11.425 | 12.698 | 14.409 | 16.882 | 10.89 | 11.852 | 17.739 | 20.375 |
Kodak09gray.bmp | 394296 | 3.181 | 11.181 | 12.337 | 12.779 | 14.323 | 10.67 | 11.536 | 13.013 | 14.12 |
Kodak12.bmp | 1179704 | 9.005 | 10.699 | 11.914 | 13.83 | 16.278 | 10.178 | 11.064 | 17.028 | 19.782 |
Kodak12gray.bmp | 394296 | 3.139 | 9.927 | 11.116 | 11.692 | 13.163 | 9.498 | 10.35 | 11.743 | 12.76 |
Kodak18.bmp | 1179704 | 5.783 | 6.156 | 6.52 | 7.472 | 8.28 | 5.936 | 6.142 | 9.393 | 10.546 |
Kodak18gray.bmp | 394296 | 2.341 | 6.481 | 6.997 | 7.591 | 8.122 | 6.256 | 6.617 | 7.677 | 7.982 |
Kodak21.bmp | 1179704 | 6.964 | 7.844 | 8.083 | 9.798 | 10.679 | 7.555 | 7.609 | 13.156 | 14.344 |
Kodak21gray.bmp | 394296 | 2.579 | 8.047 | 8.271 | 9.168 | 9.441 | 7.778 | 7.785 | 9.438 | 9.45 |
Kodak22.bmp | 1179704 | 6.776 | 7.552 | 8.242 | 9.104 | 10.474 | 7.267 | 7.702 | 11.184 | 12.678 |
Kodak22gray.bmp | 394296 | 2.598 | 7.629 | 8.416 | 8.927 | 9.819 | 7.35 | 7.849 | 9.155 | 9.502 |
Filename | Og. Size (bytes) | JPEG Compression ratio | Ratio (8/gzip) | 16/gzip | 8/bzip | 16/bzip | 8/zlib | 16/zlib | 8/xz | 16/xz |
Kodak08.bmp | 1179704 | 11.588 | 5.588 | 5.75 | 6.852 | 7.503 | 5.391 | 5.425 | 9.742 | 10.497 |
Kodak08gray.bmp | 394296 | 4.159 | 5.487 | 5.658 | 6.138 | 6.396 | 5.308 | 5.354 | 6.37 | 6.362 |
Kodak09.bmp | 1179704 | 25.103 | 13.26 | 14.973 | 17 | 20.607 | 12.669 | 13.911 | 21.304 | 24.522 |
Kodak09gray.bmp | 394296 | 8.978 | 12.891 | 14.509 | 14.942 | 17.181 | 12.319 | 13.534 | 15.077 | 16.651 |
Kodak12.bmp | 1179704 | 23.742 | 12.697 | 14.424 | 16.808 | 20.084 | 12.038 | 13.329 | 19.98 | 23.848 |
Kodak12gray.bmp | 394296 | 8.247 | 11.758 | 13.373 | 14.039 | 16.047 | 11.19 | 12.416 | 13.779 | 15.33 |
Kodak18.bmp | 1179704 | 14.199 | 7.038 | 7.69 | 8.644 | 9.929 | 6.769 | 7.206 | 10.846 | 12.466 |
Kodak18gray.bmp | 394296 | 5.643 | 7.42 | 8.197 | 8.732 | 9.639 | 7.152 | 7.711 | 8.836 | 9.411 |
Kodak21.bmp | 1179704 | 18.035 | 9.017 | 9.436 | 11.482 | 12.805 | 8.674 | 8.851 | 15.46 | 17.001 |
Kodak21gray.bmp | 394296 | 6.668 | 9.202 | 9.619 | 10.641 | 11.223 | 8.898 | 9.048 | 10.928 | 11.016 |
Kodak22.bmp | 1179704 | 17.369 | 8.742 | 9.81 | 10.658 | 12.716 | 8.371 | 9.134 | 13.012 | 15.05 |
Kodak22gray.bmp | 394296 | 6.567 | 8.861 | 10.007 | 10.383 | 11.942 | 8.497 | 9.334 | 10.593 | 11.398 |
Filename | Og. Size (bytes) | JPEG Compression ratio | Ratio (8/gzip) | 16/gzip | 8/bzip | 16/bzip | 8/zlib | 16/zlib | 8/xz | 16/xz |
Kodak08.bmp | 1179704 | 20.975 | 6.523 | 6.888 | 8.164 | 9.252 | 6.264 | 6.482 | 11.641 | 12.694 |
Kodak08gray.bmp | 394296 | 7.463 | 6.421 | 6.772 | 7.26 | 7.734 | 6.18 | 6.393 | 7.521 | 7.617 |
Kodak09.bmp | 1179704 | 46.697 | 15.782 | 18.164 | 20.887 | 25.819 | 15.088 | 16.835 | 26.118 | 30.11 |
Kodak09gray.bmp | 394296 | 17.338 | 15.289 | 17.578 | 17.994 | 21.355 | 14.694 | 16.329 | 18.174 | 20.337 |
Kodak12.bmp | 1179704 | 45.594 | 15.865 | 18.317 | 21.881 | 26.292 | 14.932 | 16.883 | 24.406 | 29.797 |
Kodak12gray.bmp | 394296 | 16.207 | 14.572 | 16.913 | 17.928 | 20.558 | 13.759 | 15.567 | 16.882 | 19.309 |
Kodak18.bmp | 1179704 | 27.137 | 8.397 | 9.499 | 10.471 | 12.558 | 8.056 | 8.856 | 13.029 | 15.361 |
Kodak18gray.bmp | 394296 | 10.709 | 8.858 | 10.11 | 10.597 | 12.161 | 8.529 | 9.473 | 10.608 | 11.641 |
Kodak21.bmp | 1179704 | 33.775 | 10.741 | 11.464 | 13.93 | 15.998 | 10.337 | 10.706 | 18.817 | 20.759 |
Kodak21gray.bmp | 394296 | 12.705 | 10.909 | 11.728 | 12.747 | 13.828 | 10.513 | 10.972 | 13.054 | 13.393 |
Kodak22.bmp | 1179704 | 33.94 | 10.621 | 12.252 | 13.319 | 16.184 | 10.133 | 11.338 | 15.513 | 18.437 |
Kodak22gray.bmp | 394296 | 12.866 | 10.763 | 12.441 | 12.922 | 15.111 | 10.296 | 11.547 | 12.724 | 14.189 |