image_processing.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. from PIL import Image
  2. from matplotlib import cm
  3. import random
  4. from skimage import color
  5. import numpy as np
  6. import ipfml.metrics as metrics
  7. import cv2
  8. from scipy import signal
  9. def get_LAB_L_SVD(image):
  10. """
  11. @brief Returns Singular values from LAB L Image information
  12. @param fig a matplotlib figure
  13. @return a Python Imaging Library (PIL) image : default size (480,640,3)
  14. Usage :
  15. >>> from PIL import Image
  16. >>> from ipfml import image_processing
  17. >>> img = Image.open('./images/test_img.png')
  18. >>> U, s, V = image_processing.get_LAB_L_SVD(img)
  19. >>> U.shape
  20. (200, 200)
  21. >>> len(s)
  22. 200
  23. >>> V.shape
  24. (200, 200)
  25. """
  26. L = metrics.get_LAB_L(image)
  27. return metrics.get_SVD(L)
  28. def get_LAB_L_SVD_s(image):
  29. """
  30. @brief Returns s (Singular values) SVD from L of LAB Image information
  31. @param PIL Image
  32. @return vector of singular values
  33. Usage :
  34. >>> from PIL import Image
  35. >>> from ipfml import image_processing
  36. >>> img = Image.open('./images/test_img.png')
  37. >>> s = image_processing.get_LAB_L_SVD_s(img)
  38. >>> len(s)
  39. 200
  40. """
  41. L = metrics.get_LAB_L(image)
  42. return metrics.get_SVD_s(L)
  43. def get_LAB_L_SVD_U(image):
  44. """
  45. @brief Returns U SVD from L of LAB Image information
  46. @param PIL Image
  47. @return vector of singular values
  48. Usage :
  49. >>> from PIL import Image
  50. >>> from ipfml import image_processing
  51. >>> img = Image.open('./images/test_img.png')
  52. >>> U = image_processing.get_LAB_L_SVD_U(img)
  53. >>> U.shape
  54. (200, 200)
  55. """
  56. L = metrics.get_LAB_L(image)
  57. return metrics.get_SVD_U(L)
  58. def get_LAB_L_SVD_V(image):
  59. """
  60. @brief Returns V SVD from L of LAB Image information
  61. @param PIL Image
  62. @return vector of singular values
  63. Usage :
  64. >>> from PIL import Image
  65. >>> from ipfml import image_processing
  66. >>> img = Image.open('./images/test_img.png')
  67. >>> V = image_processing.get_LAB_L_SVD_V(img)
  68. >>> V.shape
  69. (200, 200)
  70. """
  71. L = metrics.get_LAB_L(image)
  72. return metrics.get_SVD_V(L)
  73. def divide_in_blocks(image, block_size, pil=True):
  74. '''
  75. @brief Divide image into equal size blocks
  76. @param img - PIL Image or numpy array
  77. @param block - tuple (width, height) representing the size of each dimension of the block
  78. @param pil - kind block type (PIL by default or Numpy array)
  79. @return list containing all 2D numpy blocks (in RGB or not)
  80. Usage :
  81. >>> import numpy as np
  82. >>> from PIL import Image
  83. >>> from ipfml import image_processing
  84. >>> from ipfml import metrics
  85. >>> image_values = np.random.randint(255, size=(800, 800, 3))
  86. >>> blocks = divide_in_blocks(image_values, (20, 20))
  87. >>> len(blocks)
  88. 1600
  89. >>> blocks[0].width
  90. 20
  91. >>> blocks[0].height
  92. 20
  93. >>> img_l = Image.open('./images/test_img.png')
  94. >>> L = metrics.get_LAB_L(img_l)
  95. >>> blocks_L = divide_in_blocks(L, (100, 100))
  96. >>> len(blocks_L)
  97. 4
  98. >>> blocks_L[0].width
  99. 100
  100. '''
  101. blocks = []
  102. mode = 'RGB'
  103. # check input type (PIL Image or numpy array) and convert it if necessary
  104. if hasattr(image, 'filename'):
  105. image_array = np.array(image)
  106. else:
  107. image_array = image
  108. # check dimension of input image
  109. if image_array.ndim != 3:
  110. mode = 'L'
  111. image_width, image_height = image_array.shape
  112. else:
  113. image_width, image_height, _ = image_array.shape
  114. # check size compatibility
  115. width, height = block_size
  116. if(image_width % width != 0):
  117. raise "Width size issue, block size not compatible"
  118. if(image_height % height != 0):
  119. raise "Height size issue, block size not compatible"
  120. nb_block_width = image_width / width
  121. nb_block_height = image_height / height
  122. for i in range(int(nb_block_width)):
  123. begin_x = i * width
  124. for j in range(int(nb_block_height)):
  125. begin_y = j * height
  126. # getting sub block information
  127. current_block = image_array[begin_x:(begin_x + width), begin_y:(begin_y + height)]
  128. if pil:
  129. blocks.append(Image.fromarray(current_block.astype('uint8'), mode))
  130. else:
  131. blocks.append(current_block)
  132. return blocks
  133. def normalize_arr(arr):
  134. '''
  135. @brief Normalize data of 1D array shape
  136. @param array - array data of 1D shape
  137. Usage :
  138. >>> from ipfml import image_processing
  139. >>> import numpy as np
  140. >>> arr = np.arange(11)
  141. >>> arr_normalized = image_processing.normalize_arr(arr)
  142. >>> arr_normalized[1]
  143. 0.1
  144. '''
  145. output_arr = []
  146. max_value = max(arr)
  147. min_value = min(arr)
  148. for v in arr:
  149. output_arr.append((v - min_value) / (max_value - min_value))
  150. return output_arr
  151. def normalize_arr_with_range(arr, min, max):
  152. '''
  153. @brief Normalize data of 1D array shape
  154. @param array - array data of 1D shape
  155. Usage :
  156. >>> from ipfml import image_processing
  157. >>> import numpy as np
  158. >>> arr = np.arange(11)
  159. >>> arr_normalized = image_processing.normalize_arr_with_range(arr, 0, 20)
  160. >>> arr_normalized[1]
  161. 0.05
  162. '''
  163. output_arr = []
  164. for v in arr:
  165. output_arr.append((v - min) / (max - min))
  166. return output_arr
  167. def normalize_2D_arr(arr):
  168. """
  169. @brief Return array normalize from its min and max values
  170. @param 2D numpy array
  171. Usage :
  172. >>> from PIL import Image
  173. >>> from ipfml import image_processing
  174. >>> img = Image.open('./images/test_img.png')
  175. >>> img_mscn = image_processing.rgb_to_mscn(img)
  176. >>> img_normalized = image_processing.normalize_2D_arr(img_mscn)
  177. >>> img_normalized.shape
  178. (200, 200)
  179. """
  180. # getting min and max value from 2D array
  181. max_value = arr.max(axis=1).max()
  182. min_value = arr.min(axis=1).min()
  183. # lambda computation to normalize
  184. g = lambda x : (x - min_value) / (max_value - min_value)
  185. f = np.vectorize(g)
  186. return f(arr)
  187. def rgb_to_mscn(image):
  188. """
  189. @brief Convert RGB Image into Mean Subtracted Contrast Normalized (MSCN)
  190. @param 3D RGB image numpy array or PIL RGB image
  191. Usage :
  192. >>> from PIL import Image
  193. >>> from ipfml import image_processing
  194. >>> img = Image.open('./images/test_img.png')
  195. >>> img_mscn = image_processing.rgb_to_mscn(img)
  196. >>> img_mscn.shape
  197. (200, 200)
  198. """
  199. # check if PIL image or not
  200. img_arr = np.array(image)
  201. # convert rgb image to gray
  202. im = np.array(color.rgb2gray(img_arr)*255, 'uint8')
  203. s = 7/6
  204. blurred = cv2.GaussianBlur(im, (7, 7), s) # apply gaussian blur to the image
  205. blurred_sq = blurred * blurred
  206. sigma = cv2.GaussianBlur(im * im, (7, 7), s) # switch to -3, 3 (7, 7) before..
  207. sigma = abs(sigma - blurred_sq) ** 0.5
  208. sigma = sigma + 1.0/255 # to make sure the denominator doesn't give DivideByZero Exception
  209. structdis = (im - blurred)/sigma # final MSCN(i, j) image
  210. return structdis
  211. def rgb_to_grey_low_bits(image, bind=15):
  212. """
  213. @brief Convert RGB Image into grey image using only 4 low bits values
  214. @param 3D RGB image numpy array or PIL RGB image
  215. Usage :
  216. >>> from PIL import Image
  217. >>> from ipfml import image_processing
  218. >>> img = Image.open('./images/test_img.png')
  219. >>> low_bits_grey_img = image_processing.rgb_to_grey_low_bits(img)
  220. >>> low_bits_grey_img.shape
  221. (200, 200)
  222. """
  223. img_arr = np.array(image)
  224. grey_block = np.array(color.rgb2gray(img_arr)*255, 'uint8')
  225. return metrics.get_low_bits_img(grey_block, bind)
  226. # TODO : Check this method too...
  227. def get_random_active_block(blocks, threshold = 0.1):
  228. """
  229. @brief Find an active block from blocks and return it (randomly way)
  230. @param 2D numpy array
  231. @param threshold 0.1 by default
  232. """
  233. active_blocks = []
  234. for id, block in enumerate(blocks):
  235. arr = np.asarray(block)
  236. variance = np.var(arr.flatten())
  237. if variance >= threshold:
  238. active_blocks.append(id)
  239. r_id = random.choice(active_blocks)
  240. return np.asarray(blocks[r_id])
  241. # TODO : check this method and check how to use active block
  242. def segment_relation_in_block(block, active_block):
  243. """
  244. @brief Return bêta value to quantity relation between central segment and surrouding regions into block
  245. @param 2D numpy array
  246. """
  247. if block.ndim != 2:
  248. raise "Numpy array dimension is incorrect, expected 2."
  249. # getting middle information of numpy array
  250. x, y = block.shape
  251. if y < 4:
  252. raise "Block size too small needed at least (x, 4) shape"
  253. middle = int(y / 2)
  254. # get central segments
  255. central_segments = block[:, middle-1:middle+1]
  256. # getting surrouding parts
  257. left_part = block[:, 0:middle-1]
  258. right_part = block[:, middle+1:]
  259. surrounding_parts = np.concatenate([left_part, right_part])
  260. std_sur = np.std(surrounding_parts.flatten())
  261. std_cen = np.std(central_segments.flatten())
  262. std_block = np.std(block.flatten())
  263. print("CEN " + str(std_cen))
  264. print("SUR " + str(std_sur))
  265. print("BLOCK " + str(std_block))
  266. std_q = std_cen / std_sur
  267. # from article, it says that block if affected with noise if (std_block > 2 * beta)
  268. beta = abs(std_q - std_block) / max(std_q, std_block)
  269. return beta
  270. ### other way to compute MSCN :
  271. # TODO : Temp code, check to remove or use it
  272. def normalize_kernel(kernel):
  273. return kernel / np.sum(kernel)
  274. def gaussian_kernel2d(n, sigma):
  275. Y, X = np.indices((n, n)) - int(n/2)
  276. gaussian_kernel = 1 / (2 * np.pi * sigma ** 2) * np.exp(-(X ** 2 + Y ** 2) / (2 * sigma ** 2))
  277. return normalize_kernel(gaussian_kernel)
  278. def local_mean(image, kernel):
  279. return signal.convolve2d(image, kernel, 'same')
  280. def local_deviation(image, local_mean, kernel):
  281. "Vectorized approximation of local deviation"
  282. sigma = image ** 2
  283. sigma = signal.convolve2d(sigma, kernel, 'same')
  284. return np.sqrt(np.abs(local_mean ** 2 - sigma))
  285. def calculate_mscn_coefficients(image, kernel_size=6, sigma=7/6):
  286. # check if PIL image or not
  287. img_arr = np.array(image)
  288. #im = np.array(color.rgb2gray(img_arr)*255, 'uint8')
  289. #im = np.asarray(cv2.imread(image.filename, 0)) # read as gray scale
  290. print(img_arr.shape)
  291. C = 1/255
  292. kernel = gaussian_kernel2d(kernel_size, sigma=sigma)
  293. local_mean = signal.convolve2d(img_arr, kernel, 'same')
  294. local_var = local_deviation(img_arr, local_mean, kernel)
  295. return (img_arr - local_mean) / (local_var + C)