utils_image.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. import os
  2. import math
  3. import random
  4. import numpy as np
  5. import torch
  6. import cv2
  7. from torchvision.utils import make_grid
  8. from datetime import datetime
  9. #import matplotlib.pyplot as plt # TODO: check with Dominik, also bsrgan.py vs bsrgan_light.py
  10. os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
  11. '''
  12. # --------------------------------------------
  13. # Kai Zhang (github: https://github.com/cszn)
  14. # 03/Mar/2019
  15. # --------------------------------------------
  16. # https://github.com/twhui/SRGAN-pyTorch
  17. # https://github.com/xinntao/BasicSR
  18. # --------------------------------------------
  19. '''
  20. IMG_EXTENSIONS = ['.jpg', '.JPG', '.jpeg', '.JPEG', '.png', '.PNG', '.ppm', '.PPM', '.bmp', '.BMP', '.tif']
  21. def is_image_file(filename):
  22. return any(filename.endswith(extension) for extension in IMG_EXTENSIONS)
  23. def get_timestamp():
  24. return datetime.now().strftime('%y%m%d-%H%M%S')
  25. def imshow(x, title=None, cbar=False, figsize=None):
  26. plt.figure(figsize=figsize)
  27. plt.imshow(np.squeeze(x), interpolation='nearest', cmap='gray')
  28. if title:
  29. plt.title(title)
  30. if cbar:
  31. plt.colorbar()
  32. plt.show()
  33. def surf(Z, cmap='rainbow', figsize=None):
  34. plt.figure(figsize=figsize)
  35. ax3 = plt.axes(projection='3d')
  36. w, h = Z.shape[:2]
  37. xx = np.arange(0,w,1)
  38. yy = np.arange(0,h,1)
  39. X, Y = np.meshgrid(xx, yy)
  40. ax3.plot_surface(X,Y,Z,cmap=cmap)
  41. #ax3.contour(X,Y,Z, zdim='z',offset=-2,cmap=cmap)
  42. plt.show()
  43. '''
  44. # --------------------------------------------
  45. # get image pathes
  46. # --------------------------------------------
  47. '''
  48. def get_image_paths(dataroot):
  49. paths = None # return None if dataroot is None
  50. if dataroot is not None:
  51. paths = sorted(_get_paths_from_images(dataroot))
  52. return paths
  53. def _get_paths_from_images(path):
  54. assert os.path.isdir(path), '{:s} is not a valid directory'.format(path)
  55. images = []
  56. for dirpath, _, fnames in sorted(os.walk(path)):
  57. for fname in sorted(fnames):
  58. if is_image_file(fname):
  59. img_path = os.path.join(dirpath, fname)
  60. images.append(img_path)
  61. assert images, '{:s} has no valid image file'.format(path)
  62. return images
  63. '''
  64. # --------------------------------------------
  65. # split large images into small images
  66. # --------------------------------------------
  67. '''
  68. def patches_from_image(img, p_size=512, p_overlap=64, p_max=800):
  69. w, h = img.shape[:2]
  70. patches = []
  71. if w > p_max and h > p_max:
  72. w1 = list(np.arange(0, w-p_size, p_size-p_overlap, dtype=np.int))
  73. h1 = list(np.arange(0, h-p_size, p_size-p_overlap, dtype=np.int))
  74. w1.append(w-p_size)
  75. h1.append(h-p_size)
  76. # print(w1)
  77. # print(h1)
  78. for i in w1:
  79. for j in h1:
  80. patches.append(img[i:i+p_size, j:j+p_size,:])
  81. else:
  82. patches.append(img)
  83. return patches
  84. def imssave(imgs, img_path):
  85. """
  86. imgs: list, N images of size WxHxC
  87. """
  88. img_name, ext = os.path.splitext(os.path.basename(img_path))
  89. for i, img in enumerate(imgs):
  90. if img.ndim == 3:
  91. img = img[:, :, [2, 1, 0]]
  92. new_path = os.path.join(os.path.dirname(img_path), img_name+str('_s{:04d}'.format(i))+'.png')
  93. cv2.imwrite(new_path, img)
  94. def split_imageset(original_dataroot, taget_dataroot, n_channels=3, p_size=800, p_overlap=96, p_max=1000):
  95. """
  96. split the large images from original_dataroot into small overlapped images with size (p_size)x(p_size),
  97. and save them into taget_dataroot; only the images with larger size than (p_max)x(p_max)
  98. will be splitted.
  99. Args:
  100. original_dataroot:
  101. taget_dataroot:
  102. p_size: size of small images
  103. p_overlap: patch size in training is a good choice
  104. p_max: images with smaller size than (p_max)x(p_max) keep unchanged.
  105. """
  106. paths = get_image_paths(original_dataroot)
  107. for img_path in paths:
  108. # img_name, ext = os.path.splitext(os.path.basename(img_path))
  109. img = imread_uint(img_path, n_channels=n_channels)
  110. patches = patches_from_image(img, p_size, p_overlap, p_max)
  111. imssave(patches, os.path.join(taget_dataroot,os.path.basename(img_path)))
  112. #if original_dataroot == taget_dataroot:
  113. #del img_path
  114. '''
  115. # --------------------------------------------
  116. # makedir
  117. # --------------------------------------------
  118. '''
  119. def mkdir(path):
  120. if not os.path.exists(path):
  121. os.makedirs(path)
  122. def mkdirs(paths):
  123. if isinstance(paths, str):
  124. mkdir(paths)
  125. else:
  126. for path in paths:
  127. mkdir(path)
  128. def mkdir_and_rename(path):
  129. if os.path.exists(path):
  130. new_name = path + '_archived_' + get_timestamp()
  131. print('Path already exists. Rename it to [{:s}]'.format(new_name))
  132. os.rename(path, new_name)
  133. os.makedirs(path)
  134. '''
  135. # --------------------------------------------
  136. # read image from path
  137. # opencv is fast, but read BGR numpy image
  138. # --------------------------------------------
  139. '''
  140. # --------------------------------------------
  141. # get uint8 image of size HxWxn_channles (RGB)
  142. # --------------------------------------------
  143. def imread_uint(path, n_channels=3):
  144. # input: path
  145. # output: HxWx3(RGB or GGG), or HxWx1 (G)
  146. if n_channels == 1:
  147. img = cv2.imread(path, 0) # cv2.IMREAD_GRAYSCALE
  148. img = np.expand_dims(img, axis=2) # HxWx1
  149. elif n_channels == 3:
  150. img = cv2.imread(path, cv2.IMREAD_UNCHANGED) # BGR or G
  151. if img.ndim == 2:
  152. img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) # GGG
  153. else:
  154. img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # RGB
  155. return img
  156. # --------------------------------------------
  157. # matlab's imwrite
  158. # --------------------------------------------
  159. def imsave(img, img_path):
  160. img = np.squeeze(img)
  161. if img.ndim == 3:
  162. img = img[:, :, [2, 1, 0]]
  163. cv2.imwrite(img_path, img)
  164. def imwrite(img, img_path):
  165. img = np.squeeze(img)
  166. if img.ndim == 3:
  167. img = img[:, :, [2, 1, 0]]
  168. cv2.imwrite(img_path, img)
  169. # --------------------------------------------
  170. # get single image of size HxWxn_channles (BGR)
  171. # --------------------------------------------
  172. def read_img(path):
  173. # read image by cv2
  174. # return: Numpy float32, HWC, BGR, [0,1]
  175. img = cv2.imread(path, cv2.IMREAD_UNCHANGED) # cv2.IMREAD_GRAYSCALE
  176. img = img.astype(np.float32) / 255.
  177. if img.ndim == 2:
  178. img = np.expand_dims(img, axis=2)
  179. # some images have 4 channels
  180. if img.shape[2] > 3:
  181. img = img[:, :, :3]
  182. return img
  183. '''
  184. # --------------------------------------------
  185. # image format conversion
  186. # --------------------------------------------
  187. # numpy(single) <---> numpy(unit)
  188. # numpy(single) <---> tensor
  189. # numpy(unit) <---> tensor
  190. # --------------------------------------------
  191. '''
  192. # --------------------------------------------
  193. # numpy(single) [0, 1] <---> numpy(unit)
  194. # --------------------------------------------
  195. def uint2single(img):
  196. return np.float32(img/255.)
  197. def single2uint(img):
  198. return np.uint8((img.clip(0, 1)*255.).round())
  199. def uint162single(img):
  200. return np.float32(img/65535.)
  201. def single2uint16(img):
  202. return np.uint16((img.clip(0, 1)*65535.).round())
  203. # --------------------------------------------
  204. # numpy(unit) (HxWxC or HxW) <---> tensor
  205. # --------------------------------------------
  206. # convert uint to 4-dimensional torch tensor
  207. def uint2tensor4(img):
  208. if img.ndim == 2:
  209. img = np.expand_dims(img, axis=2)
  210. return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float().div(255.).unsqueeze(0)
  211. # convert uint to 3-dimensional torch tensor
  212. def uint2tensor3(img):
  213. if img.ndim == 2:
  214. img = np.expand_dims(img, axis=2)
  215. return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float().div(255.)
  216. # convert 2/3/4-dimensional torch tensor to uint
  217. def tensor2uint(img):
  218. img = img.data.squeeze().float().clamp_(0, 1).cpu().numpy()
  219. if img.ndim == 3:
  220. img = np.transpose(img, (1, 2, 0))
  221. return np.uint8((img*255.0).round())
  222. # --------------------------------------------
  223. # numpy(single) (HxWxC) <---> tensor
  224. # --------------------------------------------
  225. # convert single (HxWxC) to 3-dimensional torch tensor
  226. def single2tensor3(img):
  227. return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float()
  228. # convert single (HxWxC) to 4-dimensional torch tensor
  229. def single2tensor4(img):
  230. return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float().unsqueeze(0)
  231. # convert torch tensor to single
  232. def tensor2single(img):
  233. img = img.data.squeeze().float().cpu().numpy()
  234. if img.ndim == 3:
  235. img = np.transpose(img, (1, 2, 0))
  236. return img
  237. # convert torch tensor to single
  238. def tensor2single3(img):
  239. img = img.data.squeeze().float().cpu().numpy()
  240. if img.ndim == 3:
  241. img = np.transpose(img, (1, 2, 0))
  242. elif img.ndim == 2:
  243. img = np.expand_dims(img, axis=2)
  244. return img
  245. def single2tensor5(img):
  246. return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1, 3).float().unsqueeze(0)
  247. def single32tensor5(img):
  248. return torch.from_numpy(np.ascontiguousarray(img)).float().unsqueeze(0).unsqueeze(0)
  249. def single42tensor4(img):
  250. return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1, 3).float()
  251. # from skimage.io import imread, imsave
  252. def tensor2img(tensor, out_type=np.uint8, min_max=(0, 1)):
  253. '''
  254. Converts a torch Tensor into an image Numpy array of BGR channel order
  255. Input: 4D(B,(3/1),H,W), 3D(C,H,W), or 2D(H,W), any range, RGB channel order
  256. Output: 3D(H,W,C) or 2D(H,W), [0,255], np.uint8 (default)
  257. '''
  258. tensor = tensor.squeeze().float().cpu().clamp_(*min_max) # squeeze first, then clamp
  259. tensor = (tensor - min_max[0]) / (min_max[1] - min_max[0]) # to range [0,1]
  260. n_dim = tensor.dim()
  261. if n_dim == 4:
  262. n_img = len(tensor)
  263. img_np = make_grid(tensor, nrow=int(math.sqrt(n_img)), normalize=False).numpy()
  264. img_np = np.transpose(img_np[[2, 1, 0], :, :], (1, 2, 0)) # HWC, BGR
  265. elif n_dim == 3:
  266. img_np = tensor.numpy()
  267. img_np = np.transpose(img_np[[2, 1, 0], :, :], (1, 2, 0)) # HWC, BGR
  268. elif n_dim == 2:
  269. img_np = tensor.numpy()
  270. else:
  271. raise TypeError(
  272. 'Only support 4D, 3D and 2D tensor. But received with dimension: {:d}'.format(n_dim))
  273. if out_type == np.uint8:
  274. img_np = (img_np * 255.0).round()
  275. # Important. Unlike matlab, numpy.unit8() WILL NOT round by default.
  276. return img_np.astype(out_type)
  277. '''
  278. # --------------------------------------------
  279. # Augmentation, flipe and/or rotate
  280. # --------------------------------------------
  281. # The following two are enough.
  282. # (1) augmet_img: numpy image of WxHxC or WxH
  283. # (2) augment_img_tensor4: tensor image 1xCxWxH
  284. # --------------------------------------------
  285. '''
  286. def augment_img(img, mode=0):
  287. '''Kai Zhang (github: https://github.com/cszn)
  288. '''
  289. if mode == 0:
  290. return img
  291. elif mode == 1:
  292. return np.flipud(np.rot90(img))
  293. elif mode == 2:
  294. return np.flipud(img)
  295. elif mode == 3:
  296. return np.rot90(img, k=3)
  297. elif mode == 4:
  298. return np.flipud(np.rot90(img, k=2))
  299. elif mode == 5:
  300. return np.rot90(img)
  301. elif mode == 6:
  302. return np.rot90(img, k=2)
  303. elif mode == 7:
  304. return np.flipud(np.rot90(img, k=3))
  305. def augment_img_tensor4(img, mode=0):
  306. '''Kai Zhang (github: https://github.com/cszn)
  307. '''
  308. if mode == 0:
  309. return img
  310. elif mode == 1:
  311. return img.rot90(1, [2, 3]).flip([2])
  312. elif mode == 2:
  313. return img.flip([2])
  314. elif mode == 3:
  315. return img.rot90(3, [2, 3])
  316. elif mode == 4:
  317. return img.rot90(2, [2, 3]).flip([2])
  318. elif mode == 5:
  319. return img.rot90(1, [2, 3])
  320. elif mode == 6:
  321. return img.rot90(2, [2, 3])
  322. elif mode == 7:
  323. return img.rot90(3, [2, 3]).flip([2])
  324. def augment_img_tensor(img, mode=0):
  325. '''Kai Zhang (github: https://github.com/cszn)
  326. '''
  327. img_size = img.size()
  328. img_np = img.data.cpu().numpy()
  329. if len(img_size) == 3:
  330. img_np = np.transpose(img_np, (1, 2, 0))
  331. elif len(img_size) == 4:
  332. img_np = np.transpose(img_np, (2, 3, 1, 0))
  333. img_np = augment_img(img_np, mode=mode)
  334. img_tensor = torch.from_numpy(np.ascontiguousarray(img_np))
  335. if len(img_size) == 3:
  336. img_tensor = img_tensor.permute(2, 0, 1)
  337. elif len(img_size) == 4:
  338. img_tensor = img_tensor.permute(3, 2, 0, 1)
  339. return img_tensor.type_as(img)
  340. def augment_img_np3(img, mode=0):
  341. if mode == 0:
  342. return img
  343. elif mode == 1:
  344. return img.transpose(1, 0, 2)
  345. elif mode == 2:
  346. return img[::-1, :, :]
  347. elif mode == 3:
  348. img = img[::-1, :, :]
  349. img = img.transpose(1, 0, 2)
  350. return img
  351. elif mode == 4:
  352. return img[:, ::-1, :]
  353. elif mode == 5:
  354. img = img[:, ::-1, :]
  355. img = img.transpose(1, 0, 2)
  356. return img
  357. elif mode == 6:
  358. img = img[:, ::-1, :]
  359. img = img[::-1, :, :]
  360. return img
  361. elif mode == 7:
  362. img = img[:, ::-1, :]
  363. img = img[::-1, :, :]
  364. img = img.transpose(1, 0, 2)
  365. return img
  366. def augment_imgs(img_list, hflip=True, rot=True):
  367. # horizontal flip OR rotate
  368. hflip = hflip and random.random() < 0.5
  369. vflip = rot and random.random() < 0.5
  370. rot90 = rot and random.random() < 0.5
  371. def _augment(img):
  372. if hflip:
  373. img = img[:, ::-1, :]
  374. if vflip:
  375. img = img[::-1, :, :]
  376. if rot90:
  377. img = img.transpose(1, 0, 2)
  378. return img
  379. return [_augment(img) for img in img_list]
  380. '''
  381. # --------------------------------------------
  382. # modcrop and shave
  383. # --------------------------------------------
  384. '''
  385. def modcrop(img_in, scale):
  386. # img_in: Numpy, HWC or HW
  387. img = np.copy(img_in)
  388. if img.ndim == 2:
  389. H, W = img.shape
  390. H_r, W_r = H % scale, W % scale
  391. img = img[:H - H_r, :W - W_r]
  392. elif img.ndim == 3:
  393. H, W, C = img.shape
  394. H_r, W_r = H % scale, W % scale
  395. img = img[:H - H_r, :W - W_r, :]
  396. else:
  397. raise ValueError('Wrong img ndim: [{:d}].'.format(img.ndim))
  398. return img
  399. def shave(img_in, border=0):
  400. # img_in: Numpy, HWC or HW
  401. img = np.copy(img_in)
  402. h, w = img.shape[:2]
  403. img = img[border:h-border, border:w-border]
  404. return img
  405. '''
  406. # --------------------------------------------
  407. # image processing process on numpy image
  408. # channel_convert(in_c, tar_type, img_list):
  409. # rgb2ycbcr(img, only_y=True):
  410. # bgr2ycbcr(img, only_y=True):
  411. # ycbcr2rgb(img):
  412. # --------------------------------------------
  413. '''
  414. def rgb2ycbcr(img, only_y=True):
  415. '''same as matlab rgb2ycbcr
  416. only_y: only return Y channel
  417. Input:
  418. uint8, [0, 255]
  419. float, [0, 1]
  420. '''
  421. in_img_type = img.dtype
  422. img.astype(np.float32)
  423. if in_img_type != np.uint8:
  424. img *= 255.
  425. # convert
  426. if only_y:
  427. rlt = np.dot(img, [65.481, 128.553, 24.966]) / 255.0 + 16.0
  428. else:
  429. rlt = np.matmul(img, [[65.481, -37.797, 112.0], [128.553, -74.203, -93.786],
  430. [24.966, 112.0, -18.214]]) / 255.0 + [16, 128, 128]
  431. if in_img_type == np.uint8:
  432. rlt = rlt.round()
  433. else:
  434. rlt /= 255.
  435. return rlt.astype(in_img_type)
  436. def ycbcr2rgb(img):
  437. '''same as matlab ycbcr2rgb
  438. Input:
  439. uint8, [0, 255]
  440. float, [0, 1]
  441. '''
  442. in_img_type = img.dtype
  443. img.astype(np.float32)
  444. if in_img_type != np.uint8:
  445. img *= 255.
  446. # convert
  447. rlt = np.matmul(img, [[0.00456621, 0.00456621, 0.00456621], [0, -0.00153632, 0.00791071],
  448. [0.00625893, -0.00318811, 0]]) * 255.0 + [-222.921, 135.576, -276.836]
  449. if in_img_type == np.uint8:
  450. rlt = rlt.round()
  451. else:
  452. rlt /= 255.
  453. return rlt.astype(in_img_type)
  454. def bgr2ycbcr(img, only_y=True):
  455. '''bgr version of rgb2ycbcr
  456. only_y: only return Y channel
  457. Input:
  458. uint8, [0, 255]
  459. float, [0, 1]
  460. '''
  461. in_img_type = img.dtype
  462. img.astype(np.float32)
  463. if in_img_type != np.uint8:
  464. img *= 255.
  465. # convert
  466. if only_y:
  467. rlt = np.dot(img, [24.966, 128.553, 65.481]) / 255.0 + 16.0
  468. else:
  469. rlt = np.matmul(img, [[24.966, 112.0, -18.214], [128.553, -74.203, -93.786],
  470. [65.481, -37.797, 112.0]]) / 255.0 + [16, 128, 128]
  471. if in_img_type == np.uint8:
  472. rlt = rlt.round()
  473. else:
  474. rlt /= 255.
  475. return rlt.astype(in_img_type)
  476. def channel_convert(in_c, tar_type, img_list):
  477. # conversion among BGR, gray and y
  478. if in_c == 3 and tar_type == 'gray': # BGR to gray
  479. gray_list = [cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) for img in img_list]
  480. return [np.expand_dims(img, axis=2) for img in gray_list]
  481. elif in_c == 3 and tar_type == 'y': # BGR to y
  482. y_list = [bgr2ycbcr(img, only_y=True) for img in img_list]
  483. return [np.expand_dims(img, axis=2) for img in y_list]
  484. elif in_c == 1 and tar_type == 'RGB': # gray/y to BGR
  485. return [cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) for img in img_list]
  486. else:
  487. return img_list
  488. '''
  489. # --------------------------------------------
  490. # metric, PSNR and SSIM
  491. # --------------------------------------------
  492. '''
  493. # --------------------------------------------
  494. # PSNR
  495. # --------------------------------------------
  496. def calculate_psnr(img1, img2, border=0):
  497. # img1 and img2 have range [0, 255]
  498. #img1 = img1.squeeze()
  499. #img2 = img2.squeeze()
  500. if not img1.shape == img2.shape:
  501. raise ValueError('Input images must have the same dimensions.')
  502. h, w = img1.shape[:2]
  503. img1 = img1[border:h-border, border:w-border]
  504. img2 = img2[border:h-border, border:w-border]
  505. img1 = img1.astype(np.float64)
  506. img2 = img2.astype(np.float64)
  507. mse = np.mean((img1 - img2)**2)
  508. if mse == 0:
  509. return float('inf')
  510. return 20 * math.log10(255.0 / math.sqrt(mse))
  511. # --------------------------------------------
  512. # SSIM
  513. # --------------------------------------------
  514. def calculate_ssim(img1, img2, border=0):
  515. '''calculate SSIM
  516. the same outputs as MATLAB's
  517. img1, img2: [0, 255]
  518. '''
  519. #img1 = img1.squeeze()
  520. #img2 = img2.squeeze()
  521. if not img1.shape == img2.shape:
  522. raise ValueError('Input images must have the same dimensions.')
  523. h, w = img1.shape[:2]
  524. img1 = img1[border:h-border, border:w-border]
  525. img2 = img2[border:h-border, border:w-border]
  526. if img1.ndim == 2:
  527. return ssim(img1, img2)
  528. elif img1.ndim == 3:
  529. if img1.shape[2] == 3:
  530. ssims = []
  531. for i in range(3):
  532. ssims.append(ssim(img1[:,:,i], img2[:,:,i]))
  533. return np.array(ssims).mean()
  534. elif img1.shape[2] == 1:
  535. return ssim(np.squeeze(img1), np.squeeze(img2))
  536. else:
  537. raise ValueError('Wrong input image dimensions.')
  538. def ssim(img1, img2):
  539. C1 = (0.01 * 255)**2
  540. C2 = (0.03 * 255)**2
  541. img1 = img1.astype(np.float64)
  542. img2 = img2.astype(np.float64)
  543. kernel = cv2.getGaussianKernel(11, 1.5)
  544. window = np.outer(kernel, kernel.transpose())
  545. mu1 = cv2.filter2D(img1, -1, window)[5:-5, 5:-5] # valid
  546. mu2 = cv2.filter2D(img2, -1, window)[5:-5, 5:-5]
  547. mu1_sq = mu1**2
  548. mu2_sq = mu2**2
  549. mu1_mu2 = mu1 * mu2
  550. sigma1_sq = cv2.filter2D(img1**2, -1, window)[5:-5, 5:-5] - mu1_sq
  551. sigma2_sq = cv2.filter2D(img2**2, -1, window)[5:-5, 5:-5] - mu2_sq
  552. sigma12 = cv2.filter2D(img1 * img2, -1, window)[5:-5, 5:-5] - mu1_mu2
  553. ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) *
  554. (sigma1_sq + sigma2_sq + C2))
  555. return ssim_map.mean()
  556. '''
  557. # --------------------------------------------
  558. # matlab's bicubic imresize (numpy and torch) [0, 1]
  559. # --------------------------------------------
  560. '''
  561. # matlab 'imresize' function, now only support 'bicubic'
  562. def cubic(x):
  563. absx = torch.abs(x)
  564. absx2 = absx**2
  565. absx3 = absx**3
  566. return (1.5*absx3 - 2.5*absx2 + 1) * ((absx <= 1).type_as(absx)) + \
  567. (-0.5*absx3 + 2.5*absx2 - 4*absx + 2) * (((absx > 1)*(absx <= 2)).type_as(absx))
  568. def calculate_weights_indices(in_length, out_length, scale, kernel, kernel_width, antialiasing):
  569. if (scale < 1) and (antialiasing):
  570. # Use a modified kernel to simultaneously interpolate and antialias- larger kernel width
  571. kernel_width = kernel_width / scale
  572. # Output-space coordinates
  573. x = torch.linspace(1, out_length, out_length)
  574. # Input-space coordinates. Calculate the inverse mapping such that 0.5
  575. # in output space maps to 0.5 in input space, and 0.5+scale in output
  576. # space maps to 1.5 in input space.
  577. u = x / scale + 0.5 * (1 - 1 / scale)
  578. # What is the left-most pixel that can be involved in the computation?
  579. left = torch.floor(u - kernel_width / 2)
  580. # What is the maximum number of pixels that can be involved in the
  581. # computation? Note: it's OK to use an extra pixel here; if the
  582. # corresponding weights are all zero, it will be eliminated at the end
  583. # of this function.
  584. P = math.ceil(kernel_width) + 2
  585. # The indices of the input pixels involved in computing the k-th output
  586. # pixel are in row k of the indices matrix.
  587. indices = left.view(out_length, 1).expand(out_length, P) + torch.linspace(0, P - 1, P).view(
  588. 1, P).expand(out_length, P)
  589. # The weights used to compute the k-th output pixel are in row k of the
  590. # weights matrix.
  591. distance_to_center = u.view(out_length, 1).expand(out_length, P) - indices
  592. # apply cubic kernel
  593. if (scale < 1) and (antialiasing):
  594. weights = scale * cubic(distance_to_center * scale)
  595. else:
  596. weights = cubic(distance_to_center)
  597. # Normalize the weights matrix so that each row sums to 1.
  598. weights_sum = torch.sum(weights, 1).view(out_length, 1)
  599. weights = weights / weights_sum.expand(out_length, P)
  600. # If a column in weights is all zero, get rid of it. only consider the first and last column.
  601. weights_zero_tmp = torch.sum((weights == 0), 0)
  602. if not math.isclose(weights_zero_tmp[0], 0, rel_tol=1e-6):
  603. indices = indices.narrow(1, 1, P - 2)
  604. weights = weights.narrow(1, 1, P - 2)
  605. if not math.isclose(weights_zero_tmp[-1], 0, rel_tol=1e-6):
  606. indices = indices.narrow(1, 0, P - 2)
  607. weights = weights.narrow(1, 0, P - 2)
  608. weights = weights.contiguous()
  609. indices = indices.contiguous()
  610. sym_len_s = -indices.min() + 1
  611. sym_len_e = indices.max() - in_length
  612. indices = indices + sym_len_s - 1
  613. return weights, indices, int(sym_len_s), int(sym_len_e)
  614. # --------------------------------------------
  615. # imresize for tensor image [0, 1]
  616. # --------------------------------------------
  617. def imresize(img, scale, antialiasing=True):
  618. # Now the scale should be the same for H and W
  619. # input: img: pytorch tensor, CHW or HW [0,1]
  620. # output: CHW or HW [0,1] w/o round
  621. need_squeeze = True if img.dim() == 2 else False
  622. if need_squeeze:
  623. img.unsqueeze_(0)
  624. in_C, in_H, in_W = img.size()
  625. out_C, out_H, out_W = in_C, math.ceil(in_H * scale), math.ceil(in_W * scale)
  626. kernel_width = 4
  627. kernel = 'cubic'
  628. # Return the desired dimension order for performing the resize. The
  629. # strategy is to perform the resize first along the dimension with the
  630. # smallest scale factor.
  631. # Now we do not support this.
  632. # get weights and indices
  633. weights_H, indices_H, sym_len_Hs, sym_len_He = calculate_weights_indices(
  634. in_H, out_H, scale, kernel, kernel_width, antialiasing)
  635. weights_W, indices_W, sym_len_Ws, sym_len_We = calculate_weights_indices(
  636. in_W, out_W, scale, kernel, kernel_width, antialiasing)
  637. # process H dimension
  638. # symmetric copying
  639. img_aug = torch.FloatTensor(in_C, in_H + sym_len_Hs + sym_len_He, in_W)
  640. img_aug.narrow(1, sym_len_Hs, in_H).copy_(img)
  641. sym_patch = img[:, :sym_len_Hs, :]
  642. inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long()
  643. sym_patch_inv = sym_patch.index_select(1, inv_idx)
  644. img_aug.narrow(1, 0, sym_len_Hs).copy_(sym_patch_inv)
  645. sym_patch = img[:, -sym_len_He:, :]
  646. inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long()
  647. sym_patch_inv = sym_patch.index_select(1, inv_idx)
  648. img_aug.narrow(1, sym_len_Hs + in_H, sym_len_He).copy_(sym_patch_inv)
  649. out_1 = torch.FloatTensor(in_C, out_H, in_W)
  650. kernel_width = weights_H.size(1)
  651. for i in range(out_H):
  652. idx = int(indices_H[i][0])
  653. for j in range(out_C):
  654. out_1[j, i, :] = img_aug[j, idx:idx + kernel_width, :].transpose(0, 1).mv(weights_H[i])
  655. # process W dimension
  656. # symmetric copying
  657. out_1_aug = torch.FloatTensor(in_C, out_H, in_W + sym_len_Ws + sym_len_We)
  658. out_1_aug.narrow(2, sym_len_Ws, in_W).copy_(out_1)
  659. sym_patch = out_1[:, :, :sym_len_Ws]
  660. inv_idx = torch.arange(sym_patch.size(2) - 1, -1, -1).long()
  661. sym_patch_inv = sym_patch.index_select(2, inv_idx)
  662. out_1_aug.narrow(2, 0, sym_len_Ws).copy_(sym_patch_inv)
  663. sym_patch = out_1[:, :, -sym_len_We:]
  664. inv_idx = torch.arange(sym_patch.size(2) - 1, -1, -1).long()
  665. sym_patch_inv = sym_patch.index_select(2, inv_idx)
  666. out_1_aug.narrow(2, sym_len_Ws + in_W, sym_len_We).copy_(sym_patch_inv)
  667. out_2 = torch.FloatTensor(in_C, out_H, out_W)
  668. kernel_width = weights_W.size(1)
  669. for i in range(out_W):
  670. idx = int(indices_W[i][0])
  671. for j in range(out_C):
  672. out_2[j, :, i] = out_1_aug[j, :, idx:idx + kernel_width].mv(weights_W[i])
  673. if need_squeeze:
  674. out_2.squeeze_()
  675. return out_2
  676. # --------------------------------------------
  677. # imresize for numpy image [0, 1]
  678. # --------------------------------------------
  679. def imresize_np(img, scale, antialiasing=True):
  680. # Now the scale should be the same for H and W
  681. # input: img: Numpy, HWC or HW [0,1]
  682. # output: HWC or HW [0,1] w/o round
  683. img = torch.from_numpy(img)
  684. need_squeeze = True if img.dim() == 2 else False
  685. if need_squeeze:
  686. img.unsqueeze_(2)
  687. in_H, in_W, in_C = img.size()
  688. out_C, out_H, out_W = in_C, math.ceil(in_H * scale), math.ceil(in_W * scale)
  689. kernel_width = 4
  690. kernel = 'cubic'
  691. # Return the desired dimension order for performing the resize. The
  692. # strategy is to perform the resize first along the dimension with the
  693. # smallest scale factor.
  694. # Now we do not support this.
  695. # get weights and indices
  696. weights_H, indices_H, sym_len_Hs, sym_len_He = calculate_weights_indices(
  697. in_H, out_H, scale, kernel, kernel_width, antialiasing)
  698. weights_W, indices_W, sym_len_Ws, sym_len_We = calculate_weights_indices(
  699. in_W, out_W, scale, kernel, kernel_width, antialiasing)
  700. # process H dimension
  701. # symmetric copying
  702. img_aug = torch.FloatTensor(in_H + sym_len_Hs + sym_len_He, in_W, in_C)
  703. img_aug.narrow(0, sym_len_Hs, in_H).copy_(img)
  704. sym_patch = img[:sym_len_Hs, :, :]
  705. inv_idx = torch.arange(sym_patch.size(0) - 1, -1, -1).long()
  706. sym_patch_inv = sym_patch.index_select(0, inv_idx)
  707. img_aug.narrow(0, 0, sym_len_Hs).copy_(sym_patch_inv)
  708. sym_patch = img[-sym_len_He:, :, :]
  709. inv_idx = torch.arange(sym_patch.size(0) - 1, -1, -1).long()
  710. sym_patch_inv = sym_patch.index_select(0, inv_idx)
  711. img_aug.narrow(0, sym_len_Hs + in_H, sym_len_He).copy_(sym_patch_inv)
  712. out_1 = torch.FloatTensor(out_H, in_W, in_C)
  713. kernel_width = weights_H.size(1)
  714. for i in range(out_H):
  715. idx = int(indices_H[i][0])
  716. for j in range(out_C):
  717. out_1[i, :, j] = img_aug[idx:idx + kernel_width, :, j].transpose(0, 1).mv(weights_H[i])
  718. # process W dimension
  719. # symmetric copying
  720. out_1_aug = torch.FloatTensor(out_H, in_W + sym_len_Ws + sym_len_We, in_C)
  721. out_1_aug.narrow(1, sym_len_Ws, in_W).copy_(out_1)
  722. sym_patch = out_1[:, :sym_len_Ws, :]
  723. inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long()
  724. sym_patch_inv = sym_patch.index_select(1, inv_idx)
  725. out_1_aug.narrow(1, 0, sym_len_Ws).copy_(sym_patch_inv)
  726. sym_patch = out_1[:, -sym_len_We:, :]
  727. inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long()
  728. sym_patch_inv = sym_patch.index_select(1, inv_idx)
  729. out_1_aug.narrow(1, sym_len_Ws + in_W, sym_len_We).copy_(sym_patch_inv)
  730. out_2 = torch.FloatTensor(out_H, out_W, in_C)
  731. kernel_width = weights_W.size(1)
  732. for i in range(out_W):
  733. idx = int(indices_W[i][0])
  734. for j in range(out_C):
  735. out_2[:, i, j] = out_1_aug[:, idx:idx + kernel_width, j].mv(weights_W[i])
  736. if need_squeeze:
  737. out_2.squeeze_()
  738. return out_2.numpy()
  739. if __name__ == '__main__':
  740. print('---')
  741. # img = imread_uint('test.bmp', 3)
  742. # img = uint2single(img)
  743. # img_bicubic = imresize_np(img, 1/4)