Advertisement
Mat4297

ImageCluster

Mar 27th, 2024 (edited)
747
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 53.28 KB | Source Code | 0 0
  1. #align_images.py
  2. import os
  3.  
  4. import cv2
  5. import imutils
  6. import matplotlib.pyplot as plt
  7. import numpy as np
  8.  
  9.  
  10. def align_images(
  11.     image_path,
  12.     template_path,
  13.     detector="ORB",
  14.     matcher="BF",
  15.     maxFeatures=500,
  16.     keepPercent=0.2,
  17.     debug=False,
  18.     use_matplotlib=False,
  19. ):
  20.     """
  21.    Function to align two images using a specified feature detection method and feature matching method.
  22.  
  23.    Parameters:
  24.    image_path (str): Path of the input image.
  25.    template_path (str): Path of the template image.
  26.    detector (str, optional): Method to detect features. Default is "ORB". ["ORB", "SIFT", "SURF"]
  27.    matcher (str, optional): Method to match features. Default is "BF". ["BF", "FLANN", "KNN"]
  28.    maxFeatures (int, optional): Maximum number of features to detect. Default is 500.
  29.    keepPercent (float, optional): Percentage of top matches to keep. Default is 0.2.
  30.    debug (bool, optional): If True, display intermediate results. Default is False.
  31.    use_matplotlib (bool, optional): If True, use matplotlib to display results, otherwise use OpenCV. Default is False.
  32.  
  33.    Returns:
  34.    aligned: The input image aligned to the template image.
  35.    """
  36.     # Load the images from the specified path
  37.     image = cv2.imread(image_path)
  38.     template = cv2.imread(template_path)
  39.  
  40.     # Retrieve the file name
  41.     file_name = os.path.basename(image_path)
  42.  
  43.     # Convert both the input images and the template to grayscale
  44.     imageGray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  45.     templateGray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
  46.  
  47.     # use the chosen method to detect keypoints and extract local invariant features
  48.     feature_detector = None
  49.     try:
  50.         if detector == "ORB":
  51.             feature_detector = cv2.ORB_create(maxFeatures)
  52.         elif detector == "SIFT":
  53.             feature_detector = cv2.xfeatures2d.SIFT_create()
  54.         elif detector == "SURF":
  55.             feature_detector = cv2.xfeatures2d.SURF_create()
  56.             print(
  57.                 "Warning: SURF is a patented algorithm and its usage may have legal implications."
  58.             )
  59.         else:
  60.             raise ValueError("Invalid feature detector method")
  61.  
  62.     except Exception as e:
  63.         print(f"An error occurred [method: {detector}]: {str(e)}")
  64.  
  65.     if feature_detector is not None:
  66.         kpsA, descsA = feature_detector.detectAndCompute(imageGray, None)
  67.         kpsB, descsB = feature_detector.detectAndCompute(templateGray, None)
  68.     else:
  69.         print("Feature detector not initialized.")
  70.         return
  71.  
  72.     # match the features using the chosen method
  73.     if matcher == "BF":
  74.         if detector == "ORB":
  75.             method = cv2.DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMING
  76.         else:
  77.             method = cv2.DescriptorMatcher_BRUTEFORCE
  78.     elif matcher == "FLANN":
  79.         if detector == "SIFT" or detector == "SURF":
  80.             method = cv2.DescriptorMatcher_FLANNBASED
  81.         else:
  82.             print("FLANN matcher is not compatible with ORB detector")
  83.             return
  84.     elif matcher == "KNN":
  85.         if detector == "ORB":
  86.             method = cv2.DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMING
  87.         else:
  88.             method = cv2.DescriptorMatcher_BRUTEFORCE
  89.     else:
  90.         raise ValueError("Invalid matcher method")
  91.  
  92.     feature_matcher = cv2.DescriptorMatcher_create(method)
  93.  
  94.     if matcher == "KNN":
  95.         matches = feature_matcher.knnMatch(descsA, descsB, k=2)
  96.         # apply ratio test
  97.         good_matches = []
  98.         for m, n in matches:
  99.             if m.distance < 0.75 * n.distance:
  100.                 good_matches.append(m)
  101.         matches = good_matches
  102.     else:
  103.         matches = feature_matcher.match(descsA, descsB, None)
  104.  
  105.     # sort the matches by their distance (the smaller the distance, the "more similar" the features are)
  106.     matches = sorted(matches, key=lambda x: x.distance)
  107.  
  108.     # keep only the top matches
  109.     keep = int(len(matches) * keepPercent)
  110.     matches = matches[:keep]
  111.  
  112.     # if we do not have enough matches to compute a robust homography, return None
  113.     if len(matches) < 4:
  114.         print("Not enough matches to compute a robust homography.")
  115.         return None
  116.  
  117.     # check to see if we should visualize the matched keypoints
  118.     if debug:
  119.         matchedVis = cv2.drawMatches(image, kpsA, template, kpsB, matches, None)
  120.         matchedVis = imutils.resize(matchedVis, width=1000)
  121.         if use_matplotlib:
  122.             plt.imshow(cv2.cvtColor(matchedVis, cv2.COLOR_BGR2RGB))
  123.             plt.title(
  124.                 f"Name: {file_name} [Detector: {detector}, Matcher: {matcher}, Keypoints: {len(matches)}]"
  125.             )
  126.             plt.show()
  127.             plt.close()
  128.         else:
  129.             cv2.imshow(
  130.                 f"Matched Keypoints [Detector: {detector}, Matcher: {matcher}, Keypoints: {matches}]",
  131.                 matchedVis,
  132.             )
  133.             cv2.waitKey(0)
  134.  
  135.     # construct the two sets of points for the homography computation
  136.     ptsA = np.float32([kpsA[m.queryIdx].pt for m in matches])
  137.     ptsB = np.float32([kpsB[m.trainIdx].pt for m in matches])
  138.  
  139.     # compute the homography matrix between the two sets of matched points
  140.     (H, mask) = cv2.findHomography(ptsA, ptsB, method=cv2.RANSAC)
  141.  
  142.     # use the homography matrix to align the images
  143.     (h, w) = template.shape[:2]
  144.     aligned = cv2.warpPerspective(image, H, (w, h))
  145.  
  146.     # check if the images were aligned successfully
  147.     if debug and aligned is not None:
  148.         if use_matplotlib:
  149.             plt.imshow(cv2.cvtColor(aligned, cv2.COLOR_BGR2RGB))
  150.             plt.show()
  151.             plt.close()
  152.         else:
  153.             cv2.imshow(
  154.                 f"Aligned Image",
  155.                 aligned,
  156.             )
  157.             cv2.waitKey(0)
  158.  
  159.     # return the aligned image
  160.     return aligned
  161.  
  162.  
  163.  
  164.  
  165. #image_contour.py
  166. import cv2
  167. import numpy as np
  168. import matplotlib.pyplot as plt
  169. from PIL import Image
  170. from ImageProcessor import ImageProcessor
  171.  
  172.  
  173. def image_contour(
  174.     image_path,
  175.     edge_detection_method="Canny",
  176.     filter_type="GaussianBlur",
  177.     filter_radius=4,
  178.     use_matplotlib=False,
  179.     debug=False,
  180.     **kwargs,
  181. ):
  182.     """
  183.    This function applies an edge detection method to an image and optionally displays the result.
  184.  
  185.    Parameters:
  186.    image_path (str): The path to the image file.
  187.    edge_detection_method (str): The edge detection method to use ("Canny", "Sobel", or "Laplace").
  188.    filter_type (str): The type of filter to apply before edge detection.
  189.    filter_radius (int): The radius of the filter to apply.
  190.    use_matplotlib (bool): Whether to use Matplotlib to display the image. If False, OpenCV is used.
  191.    debug (bool): If True, displays the image with detected edges.
  192.  
  193.    **kwargs:
  194.    For "Canny" method:
  195.    canny_threshold1 (int): First threshold for the hysteresis procedure. Default is 100.
  196.    canny_threshold2 (int): Second threshold for the hysteresis procedure. Default is 1500.
  197.  
  198.    For "Sobel" method:
  199.    dx (int): Order of the derivative x. Default is 1.
  200.    dy (int): Order of the derivative y. Default is 1.
  201.    ksize (int): Size of the extended Sobel kernel; it must be 1, 3, 5, or 7. Default is 5.
  202.    scale (int): Optional scale factor for the computed derivative values. Default is 1.
  203.    delta (int): Optional delta value that is added to the results prior to storing them in dst. Default is 0.
  204.  
  205.    For "Laplacian" method:
  206.    ddepth (int): Desired depth of the destination image. Default is cv2.CV_64F.
  207.    ksize (int): Aperture size used to compute the second-derivative filters. Default is 1.
  208.    scale (int): Optional scale factor for the computed Laplacian values. Default is 1.
  209.    delta (int): Optional delta value that is added to the results prior to storing them in dst. Default is 0.
  210.    borderType (int): Pixel extrapolation method. Default is cv2.BORDER_DEFAULT.
  211.  
  212.    Returns:
  213.    edges (ndarray): The image data with edge detection applied.
  214.    """
  215.     # Instantiate the ImageProcessor class
  216.     processor = ImageProcessor(image_path)
  217.  
  218.     # Apply the selected filter to the image
  219.     processor.blur_filter(filter_type, radius=filter_radius)
  220.  
  221.     # Convert the PIL image to a NumPy array
  222.     image = np.array(processor.img)
  223.  
  224.     # Convert the image to grayscale
  225.     gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  226.  
  227.     # Apply the selected edge detection method
  228.     if edge_detection_method == "Canny":
  229.         edges = cv2.Canny(
  230.             gray_image,
  231.             kwargs.get("canny_threshold1", 100),
  232.             kwargs.get("canny_threshold2", 1500),
  233.             apertureSize=5,
  234.             L2gradient=True,
  235.         )
  236.     elif edge_detection_method == "Sobel":
  237.         edges = cv2.Sobel(
  238.             gray_image,
  239.             cv2.CV_64F,
  240.             dx=kwargs.get("dx", 1),
  241.             dy=kwargs.get("dy", 1),
  242.             ksize=kwargs.get("ksize", 5),
  243.             scale=kwargs.get("scale", 1),
  244.             delta=kwargs.get("delta", 0),
  245.         )
  246.     elif edge_detection_method == "Laplacian":
  247.         edges = cv2.Laplacian(
  248.             src=gray_image,
  249.             ddepth=cv2.CV_64F,
  250.             ksize=kwargs.get("ksize", 1),
  251.             scale=kwargs.get("scale", 1),
  252.             delta=kwargs.get("delta", 0),
  253.             borderType=kwargs.get("borderType", cv2.BORDER_DEFAULT),
  254.         )
  255.     else:
  256.         print(
  257.             "Invalid edge detection method. Please specify one of the following: 'Canny', 'Laplacian', or 'Sobel'."
  258.         )
  259.  
  260.     # If debug is True, display the image with detected edges
  261.     if debug:
  262.         if use_matplotlib:
  263.             plt.imshow(edges, cmap="gray")
  264.             plt.title(f"Edge Detection Method: {edge_detection_method}")
  265.             plt.show()
  266.             plt.close()
  267.         else:
  268.             cv2.imshow(f"Edge Detection Method: {edge_detection_method}", edges)
  269.             cv2.waitKey(0)
  270.             cv2.destroyAllWindows()
  271.  
  272.     # Return the image with edge detection applied
  273.     return edges
  274.  
  275. #ImageProcessor
  276. from PIL import Image, ImageFilter, ImageEnhance, ImageOps
  277. import numpy as np
  278. from sklearn.decomposition import PCA
  279. import matplotlib.pyplot as plt
  280. import os
  281.  
  282.  
  283. class ImageProcessor:
  284.     def __init__(self, image_path):
  285.         """
  286.        Initialize the ImageProcessor object with the path of the image.
  287.  
  288.        Parameters:
  289.        image_path (str): The path of the image file.
  290.        """
  291.         self.image_path = image_path
  292.         self.img = None
  293.         self.load_image()
  294.  
  295.     def load_image(self):
  296.         """
  297.        Load the image from the specified path. If the image file does not exist or cannot be opened,
  298.        an appropriate message will be printed.
  299.        """
  300.         if not os.path.exists(self.image_path):
  301.             print(f"The image file {self.image_path} does not exist.")
  302.             return
  303.  
  304.         self.img = Image.open(self.image_path)
  305.         if self.img is None:
  306.             print(f"The image file {self.image_path} cannot be opened.")
  307.             return
  308.  
  309.     def blur_filter(self, filter_type, **kwargs):
  310.         """
  311.        Apply a filter to the image. Available filters are: 'GaussianBlur', 'BoxBlur', 'MedianFilter'.
  312.  
  313.        Parameters:
  314.        filter_type (str): The type of the filter to be applied. It should be one of the following: 'GaussianBlur', 'BoxBlur', 'MedianFilter'.
  315.        **kwargs: Additional parameters that might be needed for the filters. For 'GaussianBlur' and 'BoxBlur', you can specify 'radius'. For 'MedianFilter', you can specify 'size'.
  316.        """
  317.         if self.img is None:
  318.             print("No image loaded.")
  319.             return
  320.  
  321.         if filter_type == "GaussianBlur":
  322.             self.img = self.img.filter(
  323.                 ImageFilter.GaussianBlur(kwargs.get("radius", 2))
  324.             )
  325.         elif filter_type == "BoxBlur":
  326.             self.img = self.img.filter(ImageFilter.BoxBlur(kwargs.get("radius", 2)))
  327.         elif filter_type == "MedianFilter":
  328.             self.img = self.img.filter(ImageFilter.MedianFilter(kwargs.get("size", 3)))
  329.  
  330.     def increase_brightness(self, factor=1.2):
  331.         """
  332.        Increase the brightness of the image by a certain factor.
  333.  
  334.        Parameters:
  335.        factor (float): The factor by which to increase the brightness. Default is 1.2.
  336.        """
  337.         if self.img is None:
  338.             print("No image loaded.")
  339.             return
  340.  
  341.         enhancer = ImageEnhance.Brightness(self.img)
  342.         self.img = enhancer.enhance(factor)
  343.  
  344.     def increase_saturation(self, factor=1.2):
  345.         """
  346.        Increase the saturation of the image by a certain factor.
  347.  
  348.        Parameters:
  349.        factor (float): The factor by which to increase the saturation. Default is 1.2.
  350.        """
  351.         if self.img is None:
  352.             print("No image loaded.")
  353.             return
  354.  
  355.         enhancer = ImageEnhance.Color(self.img)
  356.         self.img = enhancer.enhance(factor)
  357.  
  358.     def increase_contrast(self, factor=1.2):
  359.         """
  360.        Increase the contrast of the image by a certain factor.
  361.  
  362.        Parameters:
  363.        factor (float): The factor by which to increase the contrast. Default is 1.2.
  364.        """
  365.         if self.img is None:
  366.             print("No image loaded.")
  367.             return
  368.  
  369.         enhancer = ImageEnhance.Contrast(self.img)
  370.         self.img = enhancer.enhance(factor)
  371.  
  372.     def resize(self, size=None, factor=None, maintain_aspect_ratio=False):
  373.         """
  374.        Resize the image to the specified size or downsample it by a certain factor.
  375.  
  376.        Parameters:
  377.        size (tuple or int): The desired size of the image or the length of the longer side when maintain_aspect_ratio is True. Default is None.
  378.        factor (int): The factor by which to downsample the image. Default is None.
  379.        maintain_aspect_ratio (bool): If True, maintain the aspect ratio when resizing to a specific size. Default is False.
  380.        """
  381.         if self.img is None:
  382.             print("No image loaded.")
  383.             return
  384.  
  385.         if size is not None:
  386.             if maintain_aspect_ratio:
  387.                 width, height = self.img.size
  388.                 aspect_ratio = width / height
  389.                 if width > height:
  390.                     size = (size, int(size / aspect_ratio))
  391.                 else:
  392.                     size = (int(size * aspect_ratio), size)
  393.             else:
  394.                 if isinstance(size, int):
  395.                     size = (size, size)
  396.             self.img = self.img.resize(size)
  397.         elif factor is not None:
  398.             width, height = self.img.size
  399.             self.img = self.img.resize((width // factor, height // factor))
  400.         else:
  401.             print("Please provide either size or factor.")
  402.  
  403.     def rotate(self, angle):
  404.         """
  405.        Rotate the image by a certain angle.
  406.  
  407.        Parameters:
  408.        angle (float): The angle by which to rotate the image.
  409.        """
  410.         self.img = self.img.rotate(angle)
  411.  
  412.     def crop(self, box):
  413.         """
  414.        Crop the image to the specified box.
  415.  
  416.        Parameters:
  417.        box (tuple): The box to which to crop the image.
  418.        """
  419.         self.img = self.img.crop(box)
  420.  
  421.     def to_grayscale(self):
  422.         """
  423.        Convert the image to grayscale.
  424.        """
  425.         self.img = self.img.convert("L")
  426.  
  427.     def normalize(self):
  428.         """
  429.        Normalize the image.
  430.        This method scales the pixel values in the image to the range 0-1. This is done by dividing each pixel value by 255 (since images are 8-bit per channel, so the maximum value is 255).
  431.        """
  432.         self.img = self.img.point(lambda i: i / 255.0)
  433.  
  434.     def equalize(self):
  435.         """
  436.        Equalize the image.
  437.  
  438.        This method applies a histogram equalization to the image. Histogram equalization is a method in image processing of contrast adjustment using the image's histogram. This method usually increases the global contrast of many images, especially when the usable data of the image is represented by close contrast values.
  439.        """
  440.         if self.img is None:
  441.             print("No image loaded.")
  442.             return
  443.  
  444.         if self.img.mode == "RGBA":
  445.             # Separate the alpha channel
  446.             r, g, b, a = self.img.split()
  447.  
  448.             # Convert RGB channels to an image and equalize
  449.             rgb_image = Image.merge("RGB", (r, g, b))
  450.             equalized_rgb_image = ImageOps.equalize(rgb_image)
  451.  
  452.             # Merge equalized RGB channels and alpha channel back into an image
  453.             r, g, b = equalized_rgb_image.split()
  454.             self.img = Image.merge("RGBA", (r, g, b, a))
  455.         else:
  456.             self.img = ImageOps.equalize(self.img)
  457.  
  458.     def add_noise(self, radius=1.0):
  459.         """
  460.        Add noise to the image.
  461.  
  462.        This method applies a noise effect to the image. The effect randomly redistributes pixel values within a certain neighborhood around each pixel. The size of this neighborhood is defined by the radius parameter.
  463.  
  464.        Parameters:
  465.        radius (float): The radius defining the neighborhood for the noise effect. Default is 1.0.
  466.        """
  467.         if self.img is None:
  468.             print("No image loaded.")
  469.             return
  470.  
  471.         self.img = self.img.effect_spread(radius)
  472.  
  473.     def flip(self, direction):
  474.         """
  475.        Flip the image in the specified direction.
  476.  
  477.        Parameters:
  478.        direction (str): The direction in which to flip the image. It should be either 'horizontal' or 'vertical'.
  479.        """
  480.         if self.img is None:
  481.             print("No image loaded.")
  482.             return
  483.  
  484.         if direction == "horizontal":
  485.             self.img = self.img.transpose(Image.FLIP_LEFT_RIGHT)
  486.         elif direction == "vertical":
  487.             self.img = self.img.transpose(Image.FLIP_TOP_BOTTOM)
  488.         else:
  489.             print(
  490.                 "Invalid direction. Please provide either 'horizontal' or 'vertical'."
  491.             )
  492.  
  493.     def show_image(self, title="Image", use="Matplotlib"):
  494.         """
  495.        Show the image.
  496.  
  497.        Parameters:
  498.        title (str): The title of the image. Default is "Image".
  499.        use (str): The library to use for showing the image. Default is "Matplotlib".
  500.        """
  501.         if use == "Matplotlib":
  502.             plt.imshow(self.img)
  503.             plt.title(title)
  504.             plt.show()
  505.         else:
  506.             self.img.show(title=title)
  507.  
  508.  
  509.  
  510. #image_contour.py
  511. import cv2
  512. import numpy as np
  513. import matplotlib.pyplot as plt
  514. from PIL import Image
  515. from ImageProcessor import ImageProcessor
  516.  
  517.  
  518. def image_contour(
  519.     image_path,
  520.     edge_detection_method="Canny",
  521.     filter_type="GaussianBlur",
  522.     filter_radius=4,
  523.     use_matplotlib=False,
  524.     debug=False,
  525.     **kwargs,
  526. ):
  527.     """
  528.    This function applies an edge detection method to an image and optionally displays the result.
  529.  
  530.    Parameters:
  531.    image_path (str): The path to the image file.
  532.    edge_detection_method (str): The edge detection method to use ("Canny", "Sobel", or "Laplace").
  533.    filter_type (str): The type of filter to apply before edge detection.
  534.    filter_radius (int): The radius of the filter to apply.
  535.    use_matplotlib (bool): Whether to use Matplotlib to display the image. If False, OpenCV is used.
  536.    debug (bool): If True, displays the image with detected edges.
  537.  
  538.    **kwargs:
  539.    For "Canny" method:
  540.    canny_threshold1 (int): First threshold for the hysteresis procedure. Default is 100.
  541.    canny_threshold2 (int): Second threshold for the hysteresis procedure. Default is 1500.
  542.  
  543.    For "Sobel" method:
  544.    dx (int): Order of the derivative x. Default is 1.
  545.    dy (int): Order of the derivative y. Default is 1.
  546.    ksize (int): Size of the extended Sobel kernel; it must be 1, 3, 5, or 7. Default is 5.
  547.    scale (int): Optional scale factor for the computed derivative values. Default is 1.
  548.    delta (int): Optional delta value that is added to the results prior to storing them in dst. Default is 0.
  549.  
  550.    For "Laplacian" method:
  551.    ddepth (int): Desired depth of the destination image. Default is cv2.CV_64F.
  552.    ksize (int): Aperture size used to compute the second-derivative filters. Default is 1.
  553.    scale (int): Optional scale factor for the computed Laplacian values. Default is 1.
  554.    delta (int): Optional delta value that is added to the results prior to storing them in dst. Default is 0.
  555.    borderType (int): Pixel extrapolation method. Default is cv2.BORDER_DEFAULT.
  556.  
  557.    Returns:
  558.    edges (ndarray): The image data with edge detection applied.
  559.    """
  560.     # Instantiate the ImageProcessor class
  561.     processor = ImageProcessor(image_path)
  562.  
  563.     # Apply the selected filter to the image
  564.     processor.blur_filter(filter_type, radius=filter_radius)
  565.  
  566.     # Convert the PIL image to a NumPy array
  567.     image = np.array(processor.img)
  568.  
  569.     # Convert the image to grayscale
  570.     gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  571.  
  572.     # Apply the selected edge detection method
  573.     if edge_detection_method == "Canny":
  574.         edges = cv2.Canny(
  575.             gray_image,
  576.             kwargs.get("canny_threshold1", 100),
  577.             kwargs.get("canny_threshold2", 1500),
  578.             apertureSize=5,
  579.             L2gradient=True,
  580.         )
  581.     elif edge_detection_method == "Sobel":
  582.         edges = cv2.Sobel(
  583.             gray_image,
  584.             cv2.CV_64F,
  585.             dx=kwargs.get("dx", 1),
  586.             dy=kwargs.get("dy", 1),
  587.             ksize=kwargs.get("ksize", 5),
  588.             scale=kwargs.get("scale", 1),
  589.             delta=kwargs.get("delta", 0),
  590.         )
  591.     elif edge_detection_method == "Laplacian":
  592.         edges = cv2.Laplacian(
  593.             src=gray_image,
  594.             ddepth=cv2.CV_64F,
  595.             ksize=kwargs.get("ksize", 1),
  596.             scale=kwargs.get("scale", 1),
  597.             delta=kwargs.get("delta", 0),
  598.             borderType=kwargs.get("borderType", cv2.BORDER_DEFAULT),
  599.         )
  600.     else:
  601.         print(
  602.             "Invalid edge detection method. Please specify one of the following: 'Canny', 'Laplacian', or 'Sobel'."
  603.         )
  604.  
  605.     # If debug is True, display the image with detected edges
  606.     if debug:
  607.         if use_matplotlib:
  608.             plt.imshow(edges, cmap="gray")
  609.             plt.title(f"Edge Detection Method: {edge_detection_method}")
  610.             plt.show()
  611.             plt.close()
  612.         else:
  613.             cv2.imshow(f"Edge Detection Method: {edge_detection_method}", edges)
  614.             cv2.waitKey(0)
  615.             cv2.destroyAllWindows()
  616.  
  617.     # Return the image with edge detection applied
  618.     return edges
  619.  
  620.  
  621. #ImageAnalyzer.py
  622. import cv2
  623. import numpy as np
  624. import matplotlib.pyplot as plt
  625. from PIL import Image
  626.  
  627.  
  628. class ImageAnalyzer:
  629.     """
  630.    A class used to analyze images based on their histograms.
  631.  
  632.    ...
  633.  
  634.    Attributes
  635.    ----------
  636.    img : ndarray
  637.        The image to be analyzed.
  638.    ideal_img : ndarray
  639.        The ideal image to compare with.
  640.    threshold : float
  641.        The threshold for image similarity.
  642.  
  643.    Methods
  644.    -------
  645.    load_image(input)
  646.        Loads an image from a file path or a Pillow Image object.
  647.    calculate_histogram(img)
  648.        Calculates the histogram of an image.
  649.    compare_histograms(hist1, hist2)
  650.        Compares two histograms using the Bhattacharyya distance.
  651.    compare_images()
  652.        Compares the histograms of two images and prints whether they are similar.
  653.    draw_histograms(img, title)
  654.        Draws the histograms of an image.
  655.    analyze()
  656.        Performs the image analysis.
  657.    """
  658.  
  659.     def __init__(self, img_input, ideal_img_input):
  660.         """
  661.        Constructs all the necessary attributes for the ImageAnalyzer object.
  662.  
  663.        Parameters
  664.        ----------
  665.            img_input : str or Image
  666.                The input image file path or Pillow Image object.
  667.            ideal_img_input : str or Image
  668.                The ideal image file path or Pillow Image object.
  669.        """
  670.         self.img = self.load_image(img_input)
  671.         self.ideal_img = self.load_image(ideal_img_input)
  672.         self.threshold = 0.25
  673.  
  674.     def load_image(self, input):
  675.         """
  676.        Loads an image from a file path or a Pillow Image object.
  677.  
  678.        Parameters
  679.        ----------
  680.            input : str or Image
  681.                The input image file path or Pillow Image object.
  682.  
  683.        Returns
  684.        -------
  685.            img : ndarray
  686.                The loaded image.
  687.        """
  688.         if isinstance(input, str):  # input is a file path
  689.             return cv2.imread(input)
  690.         elif isinstance(input, Image.Image):  # input is a Pillow Image object
  691.             return cv2.cvtColor(np.array(input), cv2.COLOR_RGB2BGR)
  692.         else:
  693.             raise TypeError("Input must be a file path or a Pillow Image object.")
  694.  
  695.     def calculate_histogram(self, img):
  696.         """
  697.        Calculates the histogram of an image.
  698.  
  699.        Parameters
  700.        ----------
  701.            img : ndarray
  702.                The image.
  703.  
  704.        Returns
  705.        -------
  706.            histograms : list
  707.                The histograms of the image channels.
  708.        """
  709.         channels = cv2.split(img)
  710.         histograms = []
  711.         for channel in channels:
  712.             hist = cv2.calcHist([channel], [0], None, [256], [0, 256])
  713.             histograms.append(hist)
  714.         return histograms
  715.  
  716.     def compare_histograms(self, hist1, hist2):
  717.         """
  718.        Compares two histograms using the Bhattacharyya distance.
  719.  
  720.        Parameters
  721.        ----------
  722.            hist1 : list
  723.                The first histogram.
  724.            hist2 : list
  725.                The second histogram.
  726.  
  727.        Returns
  728.        -------
  729.            distance : float
  730.                The Bhattacharyya distance between the two histograms.
  731.        """
  732.         distance = sum(
  733.             [
  734.                 cv2.compareHist(h1, h2, cv2.HISTCMP_BHATTACHARYYA)
  735.                 for h1, h2 in zip(hist1, hist2)
  736.             ]
  737.         )
  738.         return distance
  739.  
  740.     def compare_images(self):
  741.         """
  742.        Compares the histograms of two images and prints whether they are similar.
  743.        """
  744.         img_histograms = self.calculate_histogram(self.img)
  745.         ideal_histograms = self.calculate_histogram(self.ideal_img)
  746.         distance = self.compare_histograms(img_histograms, ideal_histograms)
  747.         if distance < self.threshold:
  748.             print("The image is similar to the ideal image")
  749.         else:
  750.             print("The image is not similar to the ideal image")
  751.  
  752.     def draw_histograms(self, img, title):
  753.         """
  754.        Draws the histograms of an image.
  755.  
  756.        Parameters
  757.        ----------
  758.            img : ndarray
  759.                The image.
  760.            title : str
  761.                The title of the plot.
  762.        """
  763.         vals = img.mean(axis=2).flatten()
  764.         counts, bins = np.histogram(vals, range(257))
  765.         channels = cv2.split(img)
  766.         colors = ("b", "g", "r")
  767.         plt.bar(
  768.             bins[:-1] - 0.5,
  769.             counts,
  770.             width=1,
  771.             color="yellow",
  772.             edgecolor="none",
  773.             alpha=0.5,
  774.             label="Luminosity",
  775.         )
  776.         for channel, color in zip(channels, colors):
  777.             hist = cv2.calcHist([channel], [0], None, [256], [0, 256])
  778.             plt.plot(hist, color=color, label=color)
  779.         plt.title(title)
  780.         plt.xlim([-0.5, 255.5])
  781.         plt.legend()
  782.         plt.show()
  783.  
  784.     def analyze(self):
  785.         """
  786.        Performs the image analysis by comparing the images and drawing their histograms.
  787.        """
  788.         self.compare_images()
  789.         self.draw_histograms(self.img, "img")
  790.         self.draw_histograms(self.ideal_img, "ideal_img")
  791.  
  792. [ImageCluster.py
  793. # Librerie per l'elaborazione delle immagini
  794. from PIL import Image
  795. import numpy as np
  796.  
  797. # Librerie per il machine learning
  798. from sklearn.cluster import KMeans
  799. from scipy.spatial import distance
  800.  
  801. # Librerie per la visualizzazione dei dati
  802. import matplotlib.pyplot as plt
  803. import matplotlib.cm as cm
  804. from matplotlib.ticker import AutoMinorLocator, MaxNLocator
  805. from matplotlib.colors import ListedColormap
  806. import matplotlib.patches as mpatches
  807.  
  808. # Altre librerie
  809. from collections import Counter
  810. import os
  811.  
  812.  
  813. # aggiungi una caratteristica tipo nome file (Senza estensione) in modo da usarlo per salvare tutti i grafici
  814. class ImageCluster:
  815.     """
  816.    This class provides methods to perform color clustering on an image.
  817.    The image can be provided as a file path or as a PIL.Image object.
  818.    """
  819.  
  820.     def __init__(self, image_input):
  821.         """
  822.        Initializes the ImageCluster object.
  823.        If image_input is a string, it is treated as a file path and the image is loaded from that path.
  824.        If image_input is an instance of PIL.Image, it is used directly.
  825.        """
  826.         if isinstance(image_input, str):
  827.             self.image_path = image_input
  828.             self.filename = os.path.splitext(os.path.basename(self.image_path))[0]
  829.             self.img = Image.open(self.image_path).convert("RGBA")
  830.         elif isinstance(image_input, Image.Image):
  831.             self.img = image_input.convert("RGBA")
  832.             self.filename = "image"
  833.         else:
  834.             raise TypeError(
  835.                 "image_input deve essere un percorso dell'immagine o un'istanza di PIL.Image"
  836.             )
  837.         self.n_clusters = None
  838.         self.initial_clusters = None
  839.         self.img_array = np.array(self.img)
  840.         self.data = self.img_array.reshape(-1, 4)
  841.         self.data = self.data.astype(float)
  842.  
  843.         self.removeTransparent = False
  844.         self.labels_full = None
  845.         self.mask = None
  846.         self.clustered_img = None
  847.         self.cluster_infos = None
  848.  
  849.     def remove_transparent(self, alpha_threshold=250):
  850.         """
  851.        Removes transparent pixels from the image.
  852.        A pixel is considered transparent if its alpha value is less than alpha_threshold.
  853.        """
  854.         transparent_pixels = self.data[:, 3] <= alpha_threshold
  855.         self.data[transparent_pixels] = np.nan
  856.         self.removeTransparent = True
  857.  
  858.     def filter_alpha(self):
  859.         """
  860.        Returns a boolean mask indicating which pixels in the image are not transparent.
  861.        """
  862.         return ~np.isnan(self.data[:, 3])
  863.  
  864.     def cluster(
  865.         self, n_clusters=None, initial_clusters=None, merge_similar=False, threshold=10
  866.     ):
  867.         """
  868.        Performs color clustering on the image.
  869.        If initial_clusters is provided, it is used as initialization for the KMeans algorithm.
  870.        Otherwise, if n_clusters is provided, it is used to determine the number of clusters.
  871.        If merge_similar is True, clusters with similar colors are merged.
  872.        The threshold for determining whether two colors are similar is given by threshold.
  873.        """
  874.         self.initial_clusters = initial_clusters
  875.         if initial_clusters is not None:
  876.             self.n_clusters = self.initial_clusters.shape[0]
  877.         else:
  878.             if n_clusters is not None:
  879.                 self.n_clusters = n_clusters
  880.             else:
  881.                 print("Error, choice cluster number n_clusters")
  882.  
  883.         mask = self.filter_alpha()
  884.         self.mask = mask
  885.         data_no_nan = self.data[mask]
  886.         if self.initial_clusters is not None:
  887.             kmeans = KMeans(
  888.                 n_clusters=self.n_clusters, init=self.initial_clusters, n_init=10
  889.             )
  890.         else:
  891.             kmeans = KMeans(n_clusters=self.n_clusters, random_state=0)
  892.         self.labels = kmeans.fit_predict(data_no_nan[:, :3])  # Ignora la colonna alpha
  893.         self.center_colors = kmeans.cluster_centers_
  894.         self.labels_full = np.full(mask.shape[0], -1)
  895.         self.labels_full[mask] = self.labels
  896.  
  897.         if merge_similar:
  898.             while True:
  899.                 # Calcola la distanza euclidea tra i colori dei centri dei cluster
  900.                 distances = distance.cdist(
  901.                     self.center_colors, self.center_colors, "euclidean"
  902.                 )
  903.                 # Trova la distanza minima che non sia sulla diagonale
  904.                 min_distance = np.min(
  905.                     distances + np.eye(distances.shape[0]) * distances.max()
  906.                 )
  907.                 if min_distance >= threshold:
  908.                     break
  909.                 else:
  910.                     self.n_clusters -= 1
  911.                     kmeans = KMeans(n_clusters=self.n_clusters, random_state=0)
  912.                     self.labels = kmeans.fit_predict(
  913.                         data_no_nan[:, :3]
  914.                     )  # Ignora la colonna alpha
  915.                     self.center_colors = kmeans.cluster_centers_
  916.                     self.labels_full = np.full(mask.shape[0], -1)
  917.                     self.labels_full[mask] = self.labels
  918.  
  919.     def create_clustered_image(self):
  920.         """
  921.        Creates an image where each pixel is replaced with the color of its cluster.
  922.        """
  923.         self.clustered_img = np.zeros_like(self.img_array)
  924.         for i in range(self.img_array.shape[0]):
  925.             for j in range(self.img_array.shape[1]):
  926.                 if self.labels_full[i * self.img_array.shape[1] + j] != -1:
  927.                     self.clustered_img[i, j, :3] = self.center_colors[
  928.                         self.labels_full[i * self.img_array.shape[1] + j]
  929.                     ]
  930.                     self.clustered_img[i, j, 3] = self.data[
  931.                         i * self.img_array.shape[1] + j, 3
  932.                     ]  # Mantieni il valore alfa originale
  933.                 else:
  934.                     self.clustered_img[i, j] = [
  935.                         255,
  936.                         255,
  937.                         255,
  938.                         0,
  939.                     ]  # white or transparent
  940.  
  941.     def create_clustered_image_with_ids(self):
  942.         """
  943.        Creates an image where each pixel is replaced with the ID of its cluster.
  944.        """
  945.         # Inizializza un array bidimensionale con la stessa forma di self.img_array
  946.         self.clustered_img_with_ids = np.zeros(
  947.             (self.img_array.shape[0], self.img_array.shape[1])
  948.         )
  949.         for i in range(self.img_array.shape[0]):
  950.             for j in range(self.img_array.shape[1]):
  951.                 if self.labels_full[i * self.img_array.shape[1] + j] != -1:
  952.                     # Rimpiazza il colore con l'ID del cluster
  953.                     self.clustered_img_with_ids[i, j] = self.labels_full[
  954.                         i * self.img_array.shape[1] + j
  955.                     ]
  956.                 else:
  957.                     self.clustered_img_with_ids[i, j] = self.n_clusters + 1
  958.  
  959.     def extract_cluster_info(self):
  960.         """
  961.        Extracts information about the clusters, such as the color of the centroid, the number of pixels, and the percentage of total pixels.
  962.        """
  963.         counter = Counter(self.labels)
  964.         clusters_sorted = sorted(counter.items(), key=lambda x: x[1], reverse=True)
  965.         cluster_info = {}
  966.         total_pixels = sum(counter.values())
  967.         for i, (cluster, count) in enumerate(clusters_sorted):
  968.             cluster_info[i] = {
  969.                 "color": self.center_colors[cluster],
  970.                 "pixel_count": count,
  971.                 "total_pixel_percentage": (count / total_pixels) * 100,
  972.                 "original_position": cluster,
  973.             }
  974.         cluster_info = dict(
  975.             sorted(
  976.                 cluster_info.items(),
  977.                 key=lambda item: item[1]["pixel_count"],
  978.                 reverse=True,
  979.             )
  980.         )
  981.         self.cluster_infos = cluster_info
  982.         self.total_pixels = total_pixels
  983.  
  984.     def calculate_brightness(self, color):
  985.         """
  986.        Calculates the brightness of a color.
  987.        Brightness is defined as the average of the RGB values.
  988.        """
  989.         # Calcola la luminosità come la media dei valori RGB
  990.         return sum(color) / (3 * 255)
  991.  
  992.     def plot_original_image(self, ax=None, max_size=(1024, 1024)):
  993.         """
  994.        Displays the original image.
  995.        If ax is provided, the image is displayed on that subplot.
  996.        Otherwise, a new subplot is created.
  997.        The image is resized to max_size to avoid using too much memory.
  998.        """
  999.         # Riduci la risoluzione dell'immagine
  1000.         img = self.img.copy()
  1001.         img.thumbnail(max_size, Image.LANCZOS)
  1002.  
  1003.         if ax is None:
  1004.             ax = plt.gca()
  1005.         ax.imshow(np.array(img))
  1006.         ax.set_title("Original Image")
  1007.         ax.axis("off")
  1008.  
  1009.     def plot_clustered_image(self, ax=None, max_size=(1024, 1024)):
  1010.         """
  1011.        Displays the clustered image.
  1012.        If ax is provided, the image is displayed on that subplot.
  1013.        Otherwise, a new subplot is created.
  1014.        The image is resized to max_size to avoid using too much memory.
  1015.        """
  1016.         # Pre-calcola l'immagine raggruppata
  1017.         if self.clustered_img is None:
  1018.             self.create_clustered_image()
  1019.  
  1020.         # Riduci la risoluzione dell'immagine raggruppata
  1021.         img = Image.fromarray(self.clustered_img).convert("RGBA")
  1022.         img.thumbnail(max_size, Image.LANCZOS)
  1023.  
  1024.         if ax is None:
  1025.             ax = plt.gca()
  1026.         ax.imshow(np.array(img))
  1027.         ax.set_title("Clustered Image ({} clusters)".format(self.n_clusters))
  1028.         ax.axis("off")
  1029.  
  1030.     def plot_clustered_image_high_contrast(
  1031.         self, style="jet", show_percentage=True, dpi=100
  1032.     ):
  1033.         """
  1034.        Displays the clustered image with high contrast between the cluster colors.
  1035.        The style parameter determines the colormap used.
  1036.        If show_percentage is True, the percentage of pixels in each cluster is displayed in the legend.
  1037.        """
  1038.         # Prima assicurati di aver chiamato la funzione create_clustered_image_with_ids
  1039.         self.create_clustered_image_with_ids()
  1040.  
  1041.         # Crea una nuova figura con i DPI specificati
  1042.         fig, ax = plt.subplots(dpi=dpi)
  1043.  
  1044.         # Visualizza l'immagine
  1045.         im = ax.imshow(self.clustered_img_with_ids, cmap=style)
  1046.  
  1047.         # Crea una legenda con un rettangolo colorato per ogni etichetta di cluster
  1048.         colors = [
  1049.             im.cmap(im.norm(self.cluster_infos[i]["original_position"]))
  1050.             for i in range(self.n_clusters)
  1051.         ]
  1052.         if show_percentage:
  1053.             labels = [
  1054.                 f"Cluster {self.cluster_infos[i]['original_position']} ({self.cluster_infos[i]['total_pixel_percentage']:.2f}%)"
  1055.                 for i in range(self.n_clusters)
  1056.                 if i in self.cluster_infos
  1057.             ]
  1058.         else:
  1059.             labels = [
  1060.                 f"Cluster {self.cluster_infos[i]['original_position']}"
  1061.                 for i in range(self.n_clusters)
  1062.             ]
  1063.         patches = [
  1064.             mpatches.Patch(color=colors[i], label=labels[i]) for i in range(len(colors))
  1065.         ]
  1066.         plt.legend(
  1067.             handles=patches,
  1068.             bbox_to_anchor=(1.05, 1),
  1069.             loc=2,
  1070.             borderaxespad=0.0,
  1071.             title="Legend",
  1072.         )
  1073.  
  1074.         ax.set_title(
  1075.             "Clustered Image with High Contrast ({} clusters)".format(self.n_clusters)
  1076.         )
  1077.         ax.axis("off")
  1078.  
  1079.         # Mostra la figura
  1080.         plt.show()
  1081.  
  1082.     def plot_cluster_pie(self, ax=None, dpi=100):
  1083.         """
  1084.        Displays a pie chart showing the distribution of pixels among the clusters.
  1085.        If ax is provided, the chart is displayed on that subplot.
  1086.        Otherwise, a new subplot is created.
  1087.        """
  1088.         if ax is None:
  1089.             fig, ax = plt.subplots(dpi=dpi)
  1090.         labels = [
  1091.             f"Cluster {self.cluster_infos[i]['original_position']}"
  1092.             for i in range(self.n_clusters)
  1093.             if i in self.cluster_infos
  1094.         ]
  1095.         sizes = [
  1096.             self.cluster_infos[i]["pixel_count"]
  1097.             for i in range(self.n_clusters)
  1098.             if i in self.cluster_infos
  1099.         ]
  1100.         colors = [
  1101.             self.cluster_infos[i]["color"] / 255
  1102.             for i in range(self.n_clusters)
  1103.             if i in self.cluster_infos
  1104.         ]
  1105.         wedges, text_labels, text_percentages = ax.pie(
  1106.             sizes, labels=labels, colors=colors, startangle=90, autopct="%1.1f%%"
  1107.         )
  1108.         for i in range(len(wedges)):
  1109.             color = "white" if self.calculate_brightness(colors[i]) < 0.5 else "black"
  1110.             text_labels[i].set_color(color)
  1111.             text_percentages[i].set_color(color)
  1112.         ax.legend(
  1113.             wedges,
  1114.             labels,
  1115.             title="Clusters",
  1116.             loc="center left",
  1117.             bbox_to_anchor=(1, 0, 0.5, 1),
  1118.         )
  1119.         ax.axis("equal")
  1120.         ax.set_title("PieChart ({} clusters)".format(self.n_clusters))
  1121.  
  1122.     def plot_cluster_bar(self, ax=None, dpi=100):
  1123.         """
  1124.        Displays a bar chart showing the distribution of pixels among the clusters.
  1125.        If ax is provided, the chart is displayed on that subplot.
  1126.        Otherwise, a new subplot is created.
  1127.        """
  1128.         if ax is None:
  1129.             fig, ax = plt.subplots(dpi=dpi)
  1130.         labels = [f"Cluster {i}" for i in self.cluster_infos.keys()]
  1131.         percentages = [
  1132.             info["total_pixel_percentage"] for info in self.cluster_infos.values()
  1133.         ]
  1134.         pixel_counts = [info["pixel_count"] for info in self.cluster_infos.values()]
  1135.         colors = [info["color"] / 255 for info in self.cluster_infos.values()]
  1136.         bars = ax.bar(labels, percentages, color=colors)
  1137.         for bar, pixel_count in zip(bars, pixel_counts):
  1138.             ax.text(
  1139.                 bar.get_x() + bar.get_width() / 2,
  1140.                 bar.get_height(),
  1141.                 str(pixel_count),
  1142.                 ha="center",
  1143.                 va="bottom",
  1144.             )
  1145.  
  1146.         ax.set_xlabel("Cluster")
  1147.         ax.set_ylabel("Percentage")
  1148.  
  1149.     def plot_cumulative_barchart(self, ax=None, dpi=100):
  1150.         """
  1151.        Displays a cumulative bar chart showing the distribution of pixels among the clusters.
  1152.        If ax is provided, the chart is displayed on that subplot.
  1153.        Otherwise, a new subplot is created.
  1154.        """
  1155.         if ax is None:
  1156.             fig, ax = plt.subplots(dpi=dpi)
  1157.         bottom = 0
  1158.         for i, info in self.cluster_infos.items():
  1159.             color = info["color"] / 255
  1160.             percentage = info["total_pixel_percentage"]
  1161.             pixel_count = info["pixel_count"]
  1162.             ax.bar("Cluster", height=percentage, color=color, bottom=bottom)
  1163.             brightness = self.calculate_brightness(color)
  1164.             text_color = "white" if brightness < 0.75 else "black"
  1165.             ax.text(
  1166.                 "Cluster",
  1167.                 bottom + percentage / 2,
  1168.                 str(pixel_count),
  1169.                 ha="center",
  1170.                 va="center",
  1171.                 color=text_color,
  1172.             )
  1173.             ax.yaxis.set_minor_locator(AutoMinorLocator())
  1174.             bottom += percentage
  1175.         ax.spines["top"].set_visible(False)
  1176.         ax.spines["right"].set_visible(False)
  1177.         ax.spines["bottom"].set_visible(False)
  1178.         ax.spines["left"].set_visible(True)
  1179.         ax.axes.xaxis.set_visible(False)
  1180.  
  1181.     def plot_images(self, max_size=(1024, 1024)):
  1182.         """
  1183.        Displays the original image, the clustered image, and the high contrast clustered image side by side.
  1184.        """
  1185.         fig, axs = plt.subplots(1, 3, figsize=(20, 5))
  1186.         self.plot_original_image(ax=axs[0], max_size=max_size)
  1187.         self.plot_clustered_image(ax=axs[1], max_size=max_size)
  1188.         self.plot_clustered_image_high_contrast(ax=axs[2])
  1189.  
  1190.     def plot_image_with_grid(
  1191.         self, grid_size=50, color="white", max_size=(1024, 1024), dpi=100
  1192.     ):
  1193.         """
  1194.        Displays the original image with a grid overlaid.
  1195.        The grid size is determined by grid_size.
  1196.        The grid color is determined by color.
  1197.        The image is resized to max_size to avoid using too much memory.
  1198.        """
  1199.         fig, ax = plt.subplots(dpi=dpi)
  1200.  
  1201.         # Riduci la risoluzione dell'immagine originale
  1202.         img = self.img.copy()
  1203.         img.thumbnail(max_size, Image.LANCZOS)
  1204.  
  1205.         # Mostra l'immagine originale
  1206.         ax.imshow(np.array(img))
  1207.  
  1208.         # Aggiungi la griglia
  1209.         ax.set_xticks(np.arange(-0.5, img.size[0], grid_size), minor=True)
  1210.         ax.set_yticks(np.arange(-0.5, img.size[1], grid_size), minor=True)
  1211.         ax.grid(which="minor", color=color, linestyle="-", linewidth=2)
  1212.  
  1213.         # Imposta il titolo e nasconde gli assi
  1214.         ax.set_title("Original Image with Grid")
  1215.         ax.axis("on")
  1216.  
  1217.         plt.show()
  1218.  
  1219.     def save_plots(self):
  1220.         """
  1221.        Saves all the plots in a directory named "output/{self.filename}".
  1222.        If the directory does not exist, it is created.
  1223.        """
  1224.         # Crea la directory se non esiste
  1225.         if not os.path.exists(f"output/{self.filename}"):
  1226.             os.makedirs(f"output/{self.filename}")
  1227.         self.plot_original_image()
  1228.         plt.savefig(f"output/{self.filename}/{self.filename}.png")
  1229.         self.plot_clustered_image()
  1230.         plt.savefig(f"output/{self.filename}/{self.filename}_cluster_image.png")
  1231.         self.plot_cluster_pie()
  1232.         plt.savefig(f"output/{self.filename}/{self.filename}_piechart.png")
  1233.         self.plot_clustered_image_high_contrast()
  1234.         plt.savefig(f"output/{self.filename}/{self.filename}_high_contrast.png")
  1235.  
  1236.  
  1237. #ImagePRocessor
  1238. from PIL import Image, ImageFilter, ImageEnhance, ImageOps
  1239. import numpy as np
  1240. from sklearn.decomposition import PCA
  1241. import matplotlib.pyplot as plt
  1242. import os
  1243.  
  1244.  
  1245. class ImageProcessor:
  1246.     def __init__(self, image_path):
  1247.         """
  1248.        Initialize the ImageProcessor object with the path of the image.
  1249.  
  1250.        Parameters:
  1251.        image_path (str): The path of the image file.
  1252.        """
  1253.         self.image_path = image_path
  1254.         self.img = None
  1255.         self.load_image()
  1256.  
  1257.     def load_image(self):
  1258.         """
  1259.        Load the image from the specified path. If the image file does not exist or cannot be opened,
  1260.        an appropriate message will be printed.
  1261.        """
  1262.         if not os.path.exists(self.image_path):
  1263.             print(f"The image file {self.image_path} does not exist.")
  1264.             return
  1265.  
  1266.         self.img = Image.open(self.image_path)
  1267.         if self.img is None:
  1268.             print(f"The image file {self.image_path} cannot be opened.")
  1269.             return
  1270.  
  1271.     def blur_filter(self, filter_type, **kwargs):
  1272.         """
  1273.        Apply a filter to the image. Available filters are: 'GaussianBlur', 'BoxBlur', 'MedianFilter'.
  1274.  
  1275.        Parameters:
  1276.        filter_type (str): The type of the filter to be applied. It should be one of the following: 'GaussianBlur', 'BoxBlur', 'MedianFilter'.
  1277.        **kwargs: Additional parameters that might be needed for the filters. For 'GaussianBlur' and 'BoxBlur', you can specify 'radius'. For 'MedianFilter', you can specify 'size'.
  1278.        """
  1279.         if self.img is None:
  1280.             print("No image loaded.")
  1281.             return
  1282.  
  1283.         if filter_type == "GaussianBlur":
  1284.             self.img = self.img.filter(
  1285.                 ImageFilter.GaussianBlur(kwargs.get("radius", 2))
  1286.             )
  1287.         elif filter_type == "BoxBlur":
  1288.             self.img = self.img.filter(ImageFilter.BoxBlur(kwargs.get("radius", 2)))
  1289.         elif filter_type == "MedianFilter":
  1290.             self.img = self.img.filter(ImageFilter.MedianFilter(kwargs.get("size", 3)))
  1291.  
  1292.     def increase_brightness(self, factor=1.2):
  1293.         """
  1294.        Increase the brightness of the image by a certain factor.
  1295.  
  1296.        Parameters:
  1297.        factor (float): The factor by which to increase the brightness. Default is 1.2.
  1298.        """
  1299.         if self.img is None:
  1300.             print("No image loaded.")
  1301.             return
  1302.  
  1303.         enhancer = ImageEnhance.Brightness(self.img)
  1304.         self.img = enhancer.enhance(factor)
  1305.  
  1306.     def increase_saturation(self, factor=1.2):
  1307.         """
  1308.        Increase the saturation of the image by a certain factor.
  1309.  
  1310.        Parameters:
  1311.        factor (float): The factor by which to increase the saturation. Default is 1.2.
  1312.        """
  1313.         if self.img is None:
  1314.             print("No image loaded.")
  1315.             return
  1316.  
  1317.         enhancer = ImageEnhance.Color(self.img)
  1318.         self.img = enhancer.enhance(factor)
  1319.  
  1320.     def increase_contrast(self, factor=1.2):
  1321.         """
  1322.        Increase the contrast of the image by a certain factor.
  1323.  
  1324.        Parameters:
  1325.        factor (float): The factor by which to increase the contrast. Default is 1.2.
  1326.        """
  1327.         if self.img is None:
  1328.             print("No image loaded.")
  1329.             return
  1330.  
  1331.         enhancer = ImageEnhance.Contrast(self.img)
  1332.         self.img = enhancer.enhance(factor)
  1333.  
  1334.     def resize(self, size=None, factor=None, maintain_aspect_ratio=False):
  1335.         """
  1336.        Resize the image to the specified size or downsample it by a certain factor.
  1337.  
  1338.        Parameters:
  1339.        size (tuple or int): The desired size of the image or the length of the longer side when maintain_aspect_ratio is True. Default is None.
  1340.        factor (int): The factor by which to downsample the image. Default is None.
  1341.        maintain_aspect_ratio (bool): If True, maintain the aspect ratio when resizing to a specific size. Default is False.
  1342.        """
  1343.         if self.img is None:
  1344.             print("No image loaded.")
  1345.             return
  1346.  
  1347.         if size is not None:
  1348.             if maintain_aspect_ratio:
  1349.                 width, height = self.img.size
  1350.                 aspect_ratio = width / height
  1351.                 if width > height:
  1352.                     size = (size, int(size / aspect_ratio))
  1353.                 else:
  1354.                     size = (int(size * aspect_ratio), size)
  1355.             else:
  1356.                 if isinstance(size, int):
  1357.                     size = (size, size)
  1358.             self.img = self.img.resize(size)
  1359.         elif factor is not None:
  1360.             width, height = self.img.size
  1361.             self.img = self.img.resize((width // factor, height // factor))
  1362.         else:
  1363.             print("Please provide either size or factor.")
  1364.  
  1365.     def rotate(self, angle):
  1366.         """
  1367.        Rotate the image by a certain angle.
  1368.  
  1369.        Parameters:
  1370.        angle (float): The angle by which to rotate the image.
  1371.        """
  1372.         self.img = self.img.rotate(angle)
  1373.  
  1374.     def crop(self, box):
  1375.         """
  1376.        Crop the image to the specified box.
  1377.  
  1378.        Parameters:
  1379.        box (tuple): The box to which to crop the image.
  1380.        """
  1381.         self.img = self.img.crop(box)
  1382.  
  1383.     def to_grayscale(self):
  1384.         """
  1385.        Convert the image to grayscale.
  1386.        """
  1387.         self.img = self.img.convert("L")
  1388.  
  1389.     def normalize(self):
  1390.         """
  1391.        Normalize the image.
  1392.        This method scales the pixel values in the image to the range 0-1. This is done by dividing each pixel value by 255 (since images are 8-bit per channel, so the maximum value is 255).
  1393.        """
  1394.         self.img = self.img.point(lambda i: i / 255.0)
  1395.  
  1396.     def equalize(self):
  1397.         """
  1398.        Equalize the image.
  1399.  
  1400.        This method applies a histogram equalization to the image. Histogram equalization is a method in image processing of contrast adjustment using the image's histogram. This method usually increases the global contrast of many images, especially when the usable data of the image is represented by close contrast values.
  1401.        """
  1402.         if self.img is None:
  1403.             print("No image loaded.")
  1404.             return
  1405.  
  1406.         if self.img.mode == "RGBA":
  1407.             # Separate the alpha channel
  1408.             r, g, b, a = self.img.split()
  1409.  
  1410.             # Convert RGB channels to an image and equalize
  1411.             rgb_image = Image.merge("RGB", (r, g, b))
  1412.             equalized_rgb_image = ImageOps.equalize(rgb_image)
  1413.  
  1414.             # Merge equalized RGB channels and alpha channel back into an image
  1415.             r, g, b = equalized_rgb_image.split()
  1416.             self.img = Image.merge("RGBA", (r, g, b, a))
  1417.         else:
  1418.             self.img = ImageOps.equalize(self.img)
  1419.  
  1420.     def add_noise(self, radius=1.0):
  1421.         """
  1422.        Add noise to the image.
  1423.  
  1424.        This method applies a noise effect to the image. The effect randomly redistributes pixel values within a certain neighborhood around each pixel. The size of this neighborhood is defined by the radius parameter.
  1425.  
  1426.        Parameters:
  1427.        radius (float): The radius defining the neighborhood for the noise effect. Default is 1.0.
  1428.        """
  1429.         if self.img is None:
  1430.             print("No image loaded.")
  1431.             return
  1432.  
  1433.         self.img = self.img.effect_spread(radius)
  1434.  
  1435.     def flip(self, direction):
  1436.         """
  1437.        Flip the image in the specified direction.
  1438.  
  1439.        Parameters:
  1440.        direction (str): The direction in which to flip the image. It should be either 'horizontal' or 'vertical'.
  1441.        """
  1442.         if self.img is None:
  1443.             print("No image loaded.")
  1444.             return
  1445.  
  1446.         if direction == "horizontal":
  1447.             self.img = self.img.transpose(Image.FLIP_LEFT_RIGHT)
  1448.         elif direction == "vertical":
  1449.             self.img = self.img.transpose(Image.FLIP_TOP_BOTTOM)
  1450.         else:
  1451.             print(
  1452.                 "Invalid direction. Please provide either 'horizontal' or 'vertical'."
  1453.             )
  1454.  
  1455.     def show_image(self, title="Image", use="Matplotlib"):
  1456.         """
  1457.        Show the image.
  1458.  
  1459.        Parameters:
  1460.        title (str): The title of the image. Default is "Image".
  1461.        use (str): The library to use for showing the image. Default is "Matplotlib".
  1462.        """
  1463.         if use == "Matplotlib":
  1464.             plt.imshow(self.img)
  1465.             plt.title(title)
  1466.             plt.show()
  1467.         else:
  1468.             self.img.show(title=title)
  1469.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement