diff --git a/assignment1/solution.py b/assignment1/solution.py index a916264..ffd5219 100644 --- a/assignment1/solution.py +++ b/assignment1/solution.py @@ -12,9 +12,9 @@ import UZ_utils as uz def excercise_one() -> None: image = one_a() - #one_b(image) - #one_c(image) - #one_d(100, 200, 200, 400, image) + one_b(image) + one_c(image) + one_d(100, 200, 200, 400, image) one_e() def one_a() -> npt.NDArray[np.float64]: @@ -143,10 +143,10 @@ def excercise_two() -> None: pixels in the source image is greater or lower than the given threshold. """ two_a() - #two_b('./images/bird.jpg', 100, 20) - #two_c('./images/bird.jpg', 20, 100) - #two_d() - #two_e(uz.imread_gray('./images/bird.jpg', uz.ImageType.uint8).astype(np.uint8)) + two_b('./images/bird.jpg', 100, 20) + two_c('./images/bird.jpg', 20, 100) + two_d() + two_e(uz.imread_gray('./images/bird.jpg', uz.ImageType.uint8).astype(np.uint8)) def two_a() -> tuple[npt.NDArray[np.float64], npt.NDArray[np.uint8]]: @@ -180,48 +180,6 @@ def two_a() -> tuple[npt.NDArray[np.float64], npt.NDArray[np.uint8]]: plt.show() return (image, binary_mask) - -def my_hist_for_loop(image: npt.NDArray[np.float64], number_of_bins: int) -> npt.NDArray[np.float64]: - bin_restrictions = np.arange(0, 1, 1 / number_of_bins) - bins = np.zeros(number_of_bins).astype(np.float64) - - for pixel in image.reshape(-1): - # https://stackoverflow.com/a/16244044 - bins[np.argmax(bin_restrictions > pixel)] += 1 - - return bins / np.sum(bins) - -# Much faster implementation than for loop -def my_hist(image: npt.NDArray[np.float64], number_of_bins: int, img_typ: uz.ImageType) -> npt.NDArray[np.float64]: - if img_typ == uz.ImageType.float64: - bins = np.arange(0, 1, 1 / number_of_bins) - elif img_typ == uz.ImageType.uint8: - bins = np.arange(0, 255, 255/number_of_bins) - - # Put pixels into classes - # ex. binsize = 10 then 0.4 would map into 4 - binarray = np.digitize(image.reshape(-1), bins).astype(np.uint8) - - # Now count those values - binarray = np.unique(binarray, return_counts=True) - - counts = binarray[1].astype(np.float64) # Get the counts out of tuple - - # Check if there is any empty bin - empty_bins = [] - bins = binarray[0] - for i in range(1, number_of_bins + 1): - if i not in bins: - empty_bins.append(i) - - # Add empty bins with zeros - if empty_bins != []: - for i in empty_bins: - counts = np.insert(counts, i - 1, 0) - - return counts / np.sum(counts) - - def two_b(image_path: str, number_of_bins_first: int, number_of_bins_second: int) -> None: """ Write a function myhist that accepts a grayscale image and the number of bins that @@ -239,20 +197,20 @@ def two_b(image_path: str, number_of_bins_first: int, number_of_bins_second: int sum of all cells. Why is that? Answer: """ - image = uz.imread_gray(image_path, uz.ImageType.uint8) + image = uz_image.imread_gray(image_path, uz_image.ImageType.uint8) - H1 = my_hist(image, number_of_bins_first, uz.ImageType.uint8) - H2 = my_hist(image, number_of_bins_second, uz.ImageType.uint8) + H1 = uz_image.get_image_bins(image, number_of_bins_first) + H2 = uz_image.get_image_bins(image, number_of_bins_second) - fig, (ax0, ax1, ax2) = plt.subplots(1, 3) - fig.suptitle("Birdie and histgrams") + fig, axs = plt.subplots(1, 3) + fig.suptitle("Birdie and histograms") - ax0.imshow(image, cmap="gray") - ax0.set(title="Birdie image") - ax1.bar(np.arange(number_of_bins_first), H1) - ax1.set(title="100 bins") - ax2.bar(np.arange(number_of_bins_second), H2) - ax2.set(title="20 bins") + axs[0].imshow(image, cmap="gray") + axs[0].set(title="Birdie image") + axs[1].bar(np.arange(number_of_bins_first), H1) + axs[1].set(title="100 bins") + axs[2].bar(np.arange(number_of_bins_second), H2) + axs[2].set(title="20 bins") plt.show() @@ -264,14 +222,14 @@ def two_c(image_path: str, number_of_bins_first: int, number_of_bins_second: int difference between both versions of the function. """ - image_uint8 = uz.imread_gray(image_path, uz.ImageType.uint8) - image_float64 = uz.imread_gray(image_path, uz.ImageType.float64) + image_uint8 = uz_image.imread_gray(image_path, uz_image.ImageType.uint8) + image_float64 = uz_image.imread_gray(image_path, uz_image.ImageType.float64) - H01 = my_hist(image_uint8, number_of_bins_first, uz.ImageType.uint8) - H02 = my_hist(image_uint8, number_of_bins_second, uz.ImageType.uint8) + H01 = uz_image.get_image_bins(image_uint8, number_of_bins_first) + H02 = uz_image.get_image_bins(image_uint8, number_of_bins_second) - H11 = my_hist(image_float64, number_of_bins_first, uz.ImageType.float64) - H12 = my_hist(image_float64, number_of_bins_second, uz.ImageType.float64) + H11 = uz_image.get_image_bins(image_float64, number_of_bins_first) + H12 = uz_image.get_image_bins(image_float64, number_of_bins_second) fig, axs = plt.subplots(2, 3) fig.suptitle("Comparison between two histograms") @@ -283,7 +241,7 @@ def two_c(image_path: str, number_of_bins_first: int, number_of_bins_second: int axs[0, 2].bar(np.arange(number_of_bins_second), H02) axs[0, 2].set(title=f'{number_of_bins_second} bins used') - axs[1, 0].imshow(image_float64, cmap="gray") + axs[1, 0].imshow(image_uint8, cmap="gray") axs[1, 0].set(title="Grayscale image in uint8 representation") axs[1, 1].bar(np.arange(number_of_bins_first), H11) axs[1, 1].set(title=f'{number_of_bins_first} bins used') @@ -298,50 +256,27 @@ def two_d() -> None: your web camera and change the lighting of the room. Visualize the histograms for all images for different number of bins and interpret the results. """ - light = uz.imread_gray("./images/ROOM_LIGHTS_ON.jpg", uz.ImageType.float64) - darker = uz.imread_gray("./images/ONE_ROOM_LIGH_ON.jpg", uz.ImageType.float64) - dark = uz.imread_gray("./images/DARK.jpg", uz.ImageType.float64) + imgs = [] + bins = [20, 60, 100] + imgs.append(uz_image.imread_gray("./images/ROOM_LIGHTS_ON.jpg", uz_image.ImageType.float64)) # light + imgs.append(uz_image.imread_gray("./images/ONE_ROOM_LIGH_ON.jpg", uz_image.ImageType.float64)) # darker + imgs.append(uz_image.imread_gray("./images/DARK.jpg", uz_image.ImageType.float64)) # dark - H10 = my_hist(light, 20, uz.ImageType.float64) - H11 = my_hist(light, 60, uz.ImageType.float64) - H12 = my_hist(light, 100, uz.ImageType.float64) - H20 = my_hist(darker, 20, uz.ImageType.float64) - H21 = my_hist(darker, 60, uz.ImageType.float64) - H22 = my_hist(darker, 100, uz.ImageType.float64) - H30 = my_hist(dark, 20, uz.ImageType.float64) - H31 = my_hist(dark, 60, uz.ImageType.float64) - H32 = my_hist(dark, 100, uz.ImageType.float64) - fig, axs = plt.subplots(3, 4) - fig.suptitle("spanskiduh and histgrams") - - axs[0, 0].imshow(light, cmap="gray") - axs[0, 0].set(title="Image in light conditions") - axs[0, 1].bar(np.arange(20), H10) - axs[0, 1].set(title="Using 20 bins") - axs[0, 2].bar(np.arange(60), H11) - axs[0, 2].set(title="Using 60 bins") - axs[0, 3].bar(np.arange(100), H12) - axs[0, 3].set(title="Using 100 bins") - - axs[1, 0].imshow(darker, cmap="gray") - axs[1, 0].set(title="Image in darker conditions") - axs[1, 1].bar(np.arange(20), H20) - axs[1, 1].set(title="Using 20 bins") - axs[1, 2].bar(np.arange(60), H21) - axs[1, 2].set(title="Using 60 bins") - axs[1, 3].bar(np.arange(100), H22) - axs[1, 3].set(title="Using 100 bins") - - axs[2, 0].imshow(dark, cmap="gray") - axs[2, 0].set(title="Image in dark conditions") - axs[2, 1].bar(np.arange(20), H30) - axs[2, 1].set(title="Using 20 bins") - axs[2, 2].bar(np.arange(60), H31) - axs[2, 2].set(title="Using 60 bins") - axs[2, 3].bar(np.arange(100), H32) - axs[2, 3].set(title="Using 100 bins") + fig.suptitle("Me and my histograms") + for i in range(3): + for j in range(3): + axs[i, j+1].bar(np.arange(bins[j]), uz_image.get_image_bins(imgs[i], bins[j])) + axs[i, j+1].set(title=f"Using {bins[j]} bins") + + axs[0, 0].imshow(imgs[0], cmap="gray") + axs[0, 0].set(title="Image in light conditions") + axs[1, 0].imshow(imgs[1], cmap="gray") + axs[1, 0].set(title="Image in darker conditions") + axs[2, 0].imshow(imgs[2], cmap="gray") + axs[2, 0].set(title="Image in dark conditions") + plt.show() def two_e(image: npt.NDArray[np.uint8]): @@ -352,39 +287,7 @@ def two_e(image: npt.NDArray[np.uint8]): shows the algorithm’s results on different images. References: https://en.wikipedia.org/wiki/Otsu%27s_method """ - treshold_range = np.arange(np.max(image) + 1) - criterias = [] - - for treshold in treshold_range: - # create the thresholded image - thresholded_im = np.zeros(image.shape) - - thresholded_im[image >= treshold] = 1 - - # compute weights - nb_pixels = image.size - nb_pixels1 = np.count_nonzero(thresholded_im) - weight1 = nb_pixels1 / nb_pixels - weight0 = 1 - weight1 - - # if one the classes is empty, eg all pixels are below or above the threshold, that threshold will not be considered - # in the search for the best threshold - if weight1 == 0 or weight0 == 0: - continue - - # find all pixels belonging to each class - val_pixels1 = image[thresholded_im == 1] - val_pixels0 = image[thresholded_im == 0] - - # compute variance of these classes - var0 = np.var(val_pixels0) if len(val_pixels0) > 0 else 0 - var1 = np.var(val_pixels1) if len(val_pixels1) > 0 else 0 - - criterias.append( weight0 * var0 + weight1 * var1) - - best_threshold = treshold_range[np.argmin(criterias)] - print(f'best treshold is: {best_threshold}') - return best_threshold + return uz_image.calculate_best_treshold_using_otsu_method(image) ###################################################### @@ -392,7 +295,7 @@ def two_e(image: npt.NDArray[np.uint8]): ###################################################### def excercise_three() -> None: - #three_a() + three_a() #mask1, _ = three_b() #three_c(uz.imread('./images/bird.jpg', uz.ImageType.float64), mask1) #three_d() @@ -524,9 +427,12 @@ def three_d(): TRESHOLD = two_e(eagle_img_gray) binary_mask = eagle_img_gray.copy() - binary_mask = np.where(binary_mask < TRESHOLD, 0, 1) + binary_mask = np.where(binary_mask < TRESHOLD, 0, 1) binary_mask = uz.convert_float64_array_to_uint8_array(binary_mask) + plt.imshow(binary_mask, cmap='gray') + plt.show() + # If I would invert image here, then we would get crap # So workaround: SE_CROSS = cv2.getStructuringElement(cv2.MORPH_CROSS, (2, 2)) @@ -582,9 +488,9 @@ def three_e(): sizes = stats[:, -1] for blob in range(n_blobs): - if sizes[blob] > 700: + if sizes[blob] > COIN_SIZE: binary_mask[im_with_separated_blobs == blob] = 0 - + three_c(coin_img_color, binary_mask) diff --git a/assignment1/uz_framework/image.py b/assignment1/uz_framework/image.py index 877ed7a..e43e583 100644 --- a/assignment1/uz_framework/image.py +++ b/assignment1/uz_framework/image.py @@ -11,7 +11,7 @@ class ImageType(enum.Enum): uint8 = 0 float64 = 1 -def imread(path: str, type: ImageType) -> npt.NDArray[np.float64] or npt.NDArray[np.uint8]: +def imread(path: str, type: ImageType) -> Union[npt.NDArray[np.float64], npt.NDArray[np.uint8]]: """ Reads an image in RGB order. Image type is transformed from uint8 to float, and range of values is reduced from [0, 255] to [0, 1]. @@ -24,10 +24,10 @@ def imread(path: str, type: ImageType) -> npt.NDArray[np.float64] or npt.NDArray elif type == ImageType.uint8: return I - raise Exception("Unrecognized image format!") + raise Exception(f"Unrecognized image format! {type}") -def imread_gray(path: str, type: ImageType) -> npt.NDArray[np.float64] or npt.NDArray[np.uint8]: +def imread_gray(path: str, type: ImageType) -> Union[npt.NDArray[np.float64], npt.NDArray[np.uint8]]: """ Reads an image in gray. Image type is transformed from uint8 to float, and range of values is reduced from [0, 255] to [0, 1]. @@ -85,7 +85,7 @@ def transform_coloured_image_to_grayscale(image: npt.NDArray[np.float64]) -> npt return grayscale_image -def invert_coloured_image_part(image: npt.NDArray[np.float64] or npt.NDArray[np.uint8], startx: int, endx: int, starty: int, endy: int) -> npt.NDArray[np.float64] or npt.NDArray[np.uint8]: +def invert_coloured_image_part(image: Union[npt.NDArray[np.float64], npt.NDArray[np.uint8]], startx: int, endx: int, starty: int, endy: int) -> Union[npt.NDArray[np.float64], npt.NDArray[np.uint8]]: """ Accepts image, starting position end end position for axes x & y. Returns whole image with inverted part. """ @@ -108,13 +108,13 @@ def invert_coloured_image_part(image: npt.NDArray[np.float64] or npt.NDArray[np. raise Exception("Unrecognized image format!") -def invert_coloured_image(image: npt.NDArray[np.float64] or npt.NDArray[np.uint8]) -> npt.NDArray[np.float64] or npt.NDArray[np.uint8]: +def invert_coloured_image(image: Union[npt.NDArray[np.float64], npt.NDArray[np.uint8]]) -> Union[npt.NDArray[np.float64], npt.NDArray[np.uint8]]: """ Accepts image and inverts it """ return invert_coloured_image_part(image, 0, image.shape[0], 0, image.shape[1]) -def calculate_best_treshold_using_otsu_method(image: npt.NDArray[np.float64] or npt.NDArray[np.uint8]) -> int: +def calculate_best_treshold_using_otsu_method(image: Union[npt.NDArray[np.float64], npt.NDArray[np.uint8]]) -> int: """ Accepts image and returns best treshold using otsu method """ @@ -159,3 +159,57 @@ def calculate_best_treshold_using_otsu_method(image: npt.NDArray[np.float64] or best_threshold = treshold_range[np.argmin(criterias)] return best_threshold + + +def get_image_bins_for_loop(image: Union[npt.NDArray[np.float64], npt.NDArray[np.uint8]], number_of_bins: int) -> npt.NDArray[np.float64]: + """ + Accepts image in the float64 format or uint8, returns normailzed + image bins, histogram + """ + if image.dtype.type == np.float64 or image.dtype.type == np.uint8: + bin_restrictions = np.linspace(np.min(image), np.max(image), num=number_of_bins) + else: + raise Exception("Unrecognized image format!") + + bins = np.zeros(number_of_bins).astype(np.float64) + + for pixel in image.reshape(-1): + # https://stackoverflow.com/a/16244044 + bins[np.argmax(bin_restrictions > pixel)] += 1 + + return bins / np.sum(bins) + + +# Much faster implementation than for loop +def get_image_bins(image: Union[npt.NDArray[np.float64], npt.NDArray[np.uint8]], number_of_bins: int) -> npt.NDArray[np.float64]: + """ + Accepts image in the float64 format or uint8, returns normailzed + image bins, histogram + """ + if image.dtype.type == np.float64 or image.dtype.type == np.uint8: + bins = np.linspace(np.min(image), np.max(image), num=number_of_bins) + else: + raise Exception("Unrecognized image format!") + + # Put pixels into classes + # ex. binsize = 10 then 0.4 would map into 4 + binarray = np.digitize(image.reshape(-1), bins).astype(np.uint8) + + # Now count those values + binarray = np.unique(binarray, return_counts=True) + + counts = binarray[1].astype(np.float64) # Get the counts out of tuple + + # Check if there is any empty bin + empty_bins = [] + bins = binarray[0] + for i in range(1, number_of_bins + 1): + if i not in bins: + empty_bins.append(i) + + # Add empty bins with zeros + if empty_bins != []: + for i in empty_bins: + counts = np.insert(counts, i - 1, 0) + + return counts / np.sum(counts)