├── fig ├── fig1.png ├── lena.png ├── compare1.png └── compare2.png ├── .gitignore ├── README.md └── jpeg.py /fig/fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibosutd/feature-distillation/HEAD/fig/fig1.png -------------------------------------------------------------------------------- /fig/lena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibosutd/feature-distillation/HEAD/fig/lena.png -------------------------------------------------------------------------------- /fig/compare1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibosutd/feature-distillation/HEAD/fig/compare1.png -------------------------------------------------------------------------------- /fig/compare2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibosutd/feature-distillation/HEAD/fig/compare2.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Jupyter Notebook 7 | *.ipynb 8 | .ipynb_checkpoints 9 | 10 | # temp files directories 11 | log 12 | temp 13 | 14 | # large data files 15 | *.npy 16 | *.gz 17 | *.pkl 18 | 19 | # configuration files 20 | *.cfg 21 | 22 | # bash files 23 | *.sh 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # feature-distillation 2 | 3 | This repository implements DNN-Oriented JPEG compression introduced in the following paper 4 | 5 | [Feature Distillation: DNN-Oriented JPEG Compression Against Adversarial Examples](https://arxiv.org/abs/1803.05787) 6 | 7 | ## Introduction 8 | 9 | ![DNN-Oriented JPEG Compression][fig1] 10 | 11 | Figure 1: Diagram of DNN-Oriented JPEG compression 12 | 13 | ## Usage 14 | 15 | ``` 16 | python jpeg.py --image fig/lena.png --component dnn 17 | ``` 18 | 19 | ``` 20 | usage: jpeg.py [-h] [--image IMAGE] [--component COMPONENT] [--factor FACTOR] 21 | optional arguments: 22 | -h, --help show this help message and exit 23 | --image IMAGE image name 24 | --component COMPONENT 25 | dnn-oriented or jpeg standard 26 | --factor FACTOR compression factor 27 | ``` 28 | 29 | ## Results 30 | 31 | ![Compare][fig2] 32 | 33 | Figure 2: Standard JPEG compression (Q = 50) 34 | 35 | ![Compare][fig3] 36 | 37 | Figure 3: DNN-Oriented JPEG compression (Q = 50) 38 | 39 | ## TODO 40 | 41 | - [x] zigzag sorting 42 | - [ ] sorting by standard deviation 43 | - [ ] adversarial defense evaluation 44 | 45 | ## Contribution 46 | 47 | If you find a bug, feel free to create an issue. 48 | 49 | ## Reference 50 | + [ghallak/jpeg-python](https://github.com/ghallak/jpeg-python) 51 | 52 | 53 | 54 | [fig1]: fig/fig1.png 55 | [fig2]: fig/compare1.png 56 | [fig3]: fig/compare2.png 57 | -------------------------------------------------------------------------------- /jpeg.py: -------------------------------------------------------------------------------- 1 | from scipy import fftpack 2 | from PIL import Image 3 | import math 4 | import numpy as np 5 | import argparse 6 | 7 | 8 | def load_quantization_table(component, qs=40): 9 | # Quantization Table for JPEG Standard: https://tools.ietf.org/html/rfc2435 10 | if component == 'lum': 11 | q = np.array([[16, 11, 10, 16, 24, 40, 51, 61], 12 | [12, 12, 14, 19, 26, 58, 60, 55], 13 | [14, 13, 16, 24, 40, 57, 69, 56], 14 | [14, 17, 22, 29, 51, 87, 80, 62], 15 | [18, 22, 37, 56, 68, 109, 103, 77], 16 | [24, 35, 55, 64, 81, 104, 113, 92], 17 | [49, 64, 78, 87, 103, 121, 120, 101], 18 | [72, 92, 95, 98, 112, 100, 103, 99]]) 19 | elif component == 'chrom': 20 | q = np.array([[17, 18, 24, 47, 99, 99, 99, 99], 21 | [18, 21, 26, 66, 99, 99, 99, 99], 22 | [24, 26, 56, 99, 99, 99, 99, 99], 23 | [47, 66, 99, 99, 99, 99, 99, 99], 24 | [99, 99, 99, 99, 99, 99, 99, 99], 25 | [99, 99, 99, 99, 99, 99, 99, 99], 26 | [99, 99, 99, 99, 99, 99, 99, 99], 27 | [99, 99, 99, 99, 99, 99, 99, 99]]) 28 | elif component == 'dnn': 29 | q = np.array([[ 0, 0, 0, 0, 0, 1, 1, 1], 30 | [ 0, 0, 0, 0, 1, 1, 1, 1], 31 | [ 0, 0, 0, 1, 1, 1, 1, 1], 32 | [ 0, 0, 1, 1, 1, 1, 1, 1], 33 | [ 0, 1, 1, 1, 1, 1, 1, 1], 34 | [ 1, 1, 1, 1, 1, 1, 1, 1], 35 | [ 1, 1, 1, 1, 1, 1, 1, 1], 36 | [ 1, 1, 1, 1, 1, 1, 1, 1]]) 37 | q = q * qs + np.ones_like(q) 38 | return q 39 | 40 | def make_table(component, factor, qs=40): 41 | factor = np.clip(factor, 1, 100) 42 | if factor < 50: 43 | q = 5000 / factor 44 | else: 45 | q = 200 - factor * 2 46 | qt = (load_quantization_table(component, qs) * q + 50) / 100 47 | qt = np.clip(qt, 1, 255) 48 | return qt 49 | 50 | def quantize(block, component, factor=100): 51 | qt = make_table(component, factor) 52 | return (block / qt).round() 53 | 54 | def dequantize(block, component, factor=100): 55 | qt = make_table(component, factor) 56 | return block * qt 57 | 58 | def dct2d(block): 59 | dct_coeff = fftpack.dct(fftpack.dct(block, axis=0, norm='ortho'), 60 | axis=1, norm='ortho') 61 | return dct_coeff 62 | 63 | def idct2d(dct_coeff): 64 | block = fftpack.idct(fftpack.idct(dct_coeff, axis=0, norm='ortho'), 65 | axis=1, norm='ortho') 66 | return block 67 | 68 | def encode(npmat, component, factor): 69 | rows, cols = npmat.shape[0], npmat.shape[1] 70 | blocks_count = rows // 8 * cols // 8 71 | quant_matrix_list = [] 72 | for i in range(0, rows, 8): 73 | for j in range(0, cols, 8): 74 | for k in range(3): 75 | block = npmat[i:i+8, j:j+8, k] - 128. 76 | dct_matrix = dct2d(block) 77 | if component == 'jpeg': 78 | quant_matrix = quantize(dct_matrix, 'lum' if k == 0 else 'chrom', factor) 79 | else: 80 | quant_matrix = quantize(dct_matrix, component, factor) 81 | quant_matrix_list.append(quant_matrix) 82 | return blocks_count, quant_matrix_list 83 | 84 | def decode(blocks_count, quant_matrix_list, component, factor): 85 | block_side = 8 86 | image_side = int(math.sqrt(blocks_count)) * block_side 87 | blocks_per_line = image_side // block_side 88 | npmat = np.empty((image_side, image_side, 3)) 89 | quant_matrix_index = 0 90 | for block_index in range(blocks_count): 91 | i = block_index // blocks_per_line * block_side 92 | j = block_index % blocks_per_line * block_side 93 | for c in range(3): 94 | quant_matrix = quant_matrix_list[quant_matrix_index] 95 | quant_matrix_index += 1 96 | if component == 'jpeg': 97 | dct_matrix = dequantize(quant_matrix, 'lum' if c == 0 else 'chrom', factor) 98 | else: 99 | dct_matrix = dequantize(quant_matrix, component, factor) 100 | block = idct2d(dct_matrix) 101 | npmat[i:i+8, j:j+8, c] = block + 128. 102 | npmat = np.clip(npmat.round(), 0, 255).astype('uint8') 103 | return npmat 104 | 105 | def jpeg(npmat, component='jpeg', factor=50): 106 | cnt, coeff = encode(npmat, component, factor) 107 | npmat_decode = decode(cnt, coeff, component, factor) 108 | return npmat_decode 109 | 110 | def main(): 111 | parser = argparse.ArgumentParser() 112 | parser.add_argument('--image', type=str, default='fig/lena.png', help='image name') 113 | parser.add_argument('--component', type=str, default='jpeg', 114 | help='dnn-oriented or jpeg standard') 115 | parser.add_argument('--factor', type=int, default=50, help='compression factor') 116 | args = parser.parse_args() 117 | 118 | image = Image.open(args.image) 119 | image_npmat = np.array(image, dtype='float') 120 | image_uint8 = (image_npmat).astype('uint8') 121 | ycbcr = Image.fromarray(image_uint8, 'RGB').convert('YCbCr') 122 | npmat = np.array(ycbcr) 123 | npmat_jpeg = jpeg(npmat, component=args.component, factor=args.factor) 124 | image_obj = Image.fromarray(npmat_jpeg, 'YCbCr').convert('RGB') 125 | image_obj.save('lena_jpeg.jpg', 'JPEG') 126 | 127 | if __name__ == '__main__': 128 | main() 129 | --------------------------------------------------------------------------------