├── LICENSE ├── README.md ├── fc_bug.py └── sample_image ├── 01.jpg ├── 02.jpg ├── 03.jpg ├── out_01.jpg ├── out_02.jpg └── out_03.jpg /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 nekopla 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fc_bug 2 | 3 | This is a script that creates a buggy photo like NES (Family Computer). 4 | 5 | ## Samples 6 | input image: 7 | 8 | ![sample 01](sample_image/01.jpg) 9 | 10 | output image: 11 | 12 | ![sample 01 output](sample_image/out_01.jpg) 13 | 14 | input image: 15 | 16 | ![sample 02](sample_image/02.jpg) 17 | 18 | output image: 19 | 20 | ![sample 02 output](sample_image/out_02.jpg) 21 | 22 | input image: 23 | 24 | ![sample 03](sample_image/03.jpg) 25 | 26 | output image: 27 | 28 | ![sample 03 output](sample_image/out_03.jpg) 29 | 30 | ## Usage 31 | 32 | ```$ python fc_bug.py [file_name or directory_name]``` 33 | 34 | If you put a directory's name, all images in the directory will be processed at once. 35 | -------------------------------------------------------------------------------- /fc_bug.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import sys 3 | import os 4 | import random 5 | import glob 6 | import numpy as np 7 | from scipy.fftpack import dct 8 | 9 | def create_bug(filename): 10 | src_img = cv2.imread(filename) 11 | height, width = src_img.shape[:2] 12 | 13 | #-------------------------------- 14 | # crop image 15 | #-------------------------------- 16 | DIVISION_NUM = 32 17 | block_size = int(width / DIVISION_NUM) 18 | 19 | crop_size = [height % block_size, width % block_size] 20 | start_pos = [int(crop_size[0] / 2), int(crop_size[1] / 2)] 21 | end_pos = [height - (crop_size[0] - start_pos[0]), width - (crop_size[1] - start_pos[1])] 22 | src_img = src_img[start_pos[0]:end_pos[0], start_pos[1]:end_pos[1], :] 23 | 24 | height, width = src_img.shape[:2] 25 | 26 | # parameters 27 | dst_img = np.zeros((height, width, 3), dtype = np.uint8) 28 | 29 | num_of_blocks = int((height / block_size) * (width / block_size)) 30 | block_num_y = int(height / block_size) 31 | block_num_x = int(width / block_size) 32 | 33 | block_order = [] 34 | 35 | # initial position 36 | for i in range(num_of_blocks): 37 | block_order.append(i) 38 | 39 | #-------------------------------- 40 | # random shuffle 41 | #-------------------------------- 42 | for i in range(int(num_of_blocks/1.5)): 43 | block_order[random.randrange(num_of_blocks)] = random.randrange(num_of_blocks) 44 | 45 | #-------------------------------- 46 | # similar block paste 47 | #-------------------------------- 48 | gray_img = cv2.cvtColor(src_img, cv2.COLOR_RGB2GRAY) 49 | 50 | # find low and high frequency blocks(DCT) 51 | dct_dc = [] 52 | dct_ac = [] 53 | for dst_block_y in range(block_num_y): 54 | for dst_block_x in range(block_num_x): 55 | xx = dst_block_x * block_size 56 | yy = dst_block_y * block_size 57 | dct_img = dct(dct(gray_img[yy:yy+block_size, xx:xx+block_size], axis=0, norm='ortho'), axis=1, norm='ortho') 58 | 59 | dct_dc.append(int(dct_img[0, 0])) # DC coeff 60 | dct_ac.append(int(dct_img[int(block_size/2), int(block_size/2)])) # AC(middle) coeff 61 | 62 | dc_max_idx = dct_dc.index(max(dct_dc)) 63 | ac_max_idx = dct_ac.index(sorted(dct_ac)[-random.randrange(5)]) 64 | 65 | # choose replaced block 66 | dst_block = dc_max_idx 67 | dst_block_y, dst_block_x = divmod(dst_block, block_num_x) # q, mod 68 | 69 | # templete matching 70 | dst_y = dst_block_y * block_size 71 | dst_x = dst_block_x * block_size 72 | 73 | res = cv2.matchTemplate(gray_img, gray_img[dst_y:(dst_y+block_size), dst_x:(dst_x+block_size)], cv2.TM_SQDIFF_NORMED) 74 | loc = np.where(res < 0.1) 75 | 76 | # paste 77 | src_block = ac_max_idx 78 | match_num = 0 79 | for pt in zip(*loc[::-1]): 80 | if (pt[0] % block_size) == 0 and (pt[1] % block_size) == 0: 81 | block_no = int((pt[1] * block_num_x + pt[0]) / block_size) 82 | block_order[block_no] = src_block 83 | 84 | match_num += 1 85 | 86 | #-------------------------------- 87 | # vertical paste 88 | #-------------------------------- 89 | if match_num < 20: 90 | for i in range(3): 91 | col = random.randrange(int(width / block_size)) 92 | 93 | for j in range(random.randrange(1, 3)): 94 | src_block = random.randrange(num_of_blocks) 95 | 96 | for k in range(col + j, num_of_blocks, int(width / block_size)): 97 | block_order[k] = src_block 98 | 99 | #-------------------------------- 100 | # random change color 101 | #-------------------------------- 102 | for i in range(50): 103 | target_block = random.randrange(num_of_blocks) 104 | target_block_y, target_block_x = divmod(target_block, block_num_x) # q, mod 105 | target_img_y = target_block_y * block_size 106 | target_img_x = target_block_x * block_size 107 | rgb = random.randrange(3) 108 | src_img[target_img_y:(target_img_y+block_size), target_img_x:(target_img_x+block_size), rgb] -= 128 109 | 110 | #-------------------------------- 111 | # create output image 112 | #-------------------------------- 113 | for dst_block_y in range(block_num_y): 114 | for dst_block_x in range(block_num_x): 115 | curr_pos = dst_block_y * block_num_x + dst_block_x 116 | src_block_y, src_block_x = divmod(block_order[curr_pos], block_num_x) # q, mod 117 | 118 | src_img_y = src_block_y * block_size 119 | src_img_x = src_block_x * block_size 120 | dst_img_y = dst_block_y * block_size 121 | dst_img_x = dst_block_x * block_size 122 | 123 | dst_img[dst_img_y:(dst_img_y+block_size), dst_img_x:(dst_img_x+block_size), :] = src_img[src_img_y:(src_img_y+block_size), src_img_x:(src_img_x+block_size), :] 124 | 125 | #-------------------------------- 126 | # save image 127 | #-------------------------------- 128 | dirname = os.path.dirname(filename) 129 | if dirname == '': dirname = '.' 130 | basename = os.path.basename(filename) 131 | outpath = '{}/out_{}.jpg'.format(dirname, basename[:-4]) 132 | cv2.imwrite(outpath, dst_img) 133 | print('output:', outpath) 134 | 135 | 136 | if __name__ == '__main__': 137 | args = sys.argv 138 | if len(args) < 2: 139 | print("Please input file_name or directory_name.") 140 | exit() 141 | else: 142 | filename = args[1] 143 | 144 | # directory 145 | if os.path.isdir(filename): 146 | types = ['*.jpg', '*.jpeg', '*.png', '*.bmp'] 147 | paths = [] 148 | for t in types: 149 | paths.extend(glob.glob(os.path.join(filename+'/', t))) 150 | 151 | for p in paths: 152 | print('input:', p) 153 | if os.path.basename(p)[:4] == 'out_': 154 | print('skip') 155 | continue 156 | create_bug(p) 157 | 158 | # single file 159 | else: 160 | create_bug(filename) 161 | -------------------------------------------------------------------------------- /sample_image/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekopla/fc_bug/b63c864a253ee4ea4b81eccffd5094958f214af1/sample_image/01.jpg -------------------------------------------------------------------------------- /sample_image/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekopla/fc_bug/b63c864a253ee4ea4b81eccffd5094958f214af1/sample_image/02.jpg -------------------------------------------------------------------------------- /sample_image/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekopla/fc_bug/b63c864a253ee4ea4b81eccffd5094958f214af1/sample_image/03.jpg -------------------------------------------------------------------------------- /sample_image/out_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekopla/fc_bug/b63c864a253ee4ea4b81eccffd5094958f214af1/sample_image/out_01.jpg -------------------------------------------------------------------------------- /sample_image/out_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekopla/fc_bug/b63c864a253ee4ea4b81eccffd5094958f214af1/sample_image/out_02.jpg -------------------------------------------------------------------------------- /sample_image/out_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekopla/fc_bug/b63c864a253ee4ea4b81eccffd5094958f214af1/sample_image/out_03.jpg --------------------------------------------------------------------------------