Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #align_images.py
- import os
- import cv2
- import imutils
- import matplotlib.pyplot as plt
- import numpy as np
- def align_images(
- image_path,
- template_path,
- detector="ORB",
- matcher="BF",
- maxFeatures=500,
- keepPercent=0.2,
- debug=False,
- use_matplotlib=False,
- ):
- """
- Function to align two images using a specified feature detection method and feature matching method.
- Parameters:
- image_path (str): Path of the input image.
- template_path (str): Path of the template image.
- detector (str, optional): Method to detect features. Default is "ORB". ["ORB", "SIFT", "SURF"]
- matcher (str, optional): Method to match features. Default is "BF". ["BF", "FLANN", "KNN"]
- maxFeatures (int, optional): Maximum number of features to detect. Default is 500.
- keepPercent (float, optional): Percentage of top matches to keep. Default is 0.2.
- debug (bool, optional): If True, display intermediate results. Default is False.
- use_matplotlib (bool, optional): If True, use matplotlib to display results, otherwise use OpenCV. Default is False.
- Returns:
- aligned: The input image aligned to the template image.
- """
- # Load the images from the specified path
- image = cv2.imread(image_path)
- template = cv2.imread(template_path)
- # Retrieve the file name
- file_name = os.path.basename(image_path)
- # Convert both the input images and the template to grayscale
- imageGray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
- templateGray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
- # use the chosen method to detect keypoints and extract local invariant features
- feature_detector = None
- try:
- if detector == "ORB":
- feature_detector = cv2.ORB_create(maxFeatures)
- elif detector == "SIFT":
- feature_detector = cv2.xfeatures2d.SIFT_create()
- elif detector == "SURF":
- feature_detector = cv2.xfeatures2d.SURF_create()
- print(
- "Warning: SURF is a patented algorithm and its usage may have legal implications."
- )
- else:
- raise ValueError("Invalid feature detector method")
- except Exception as e:
- print(f"An error occurred [method: {detector}]: {str(e)}")
- if feature_detector is not None:
- kpsA, descsA = feature_detector.detectAndCompute(imageGray, None)
- kpsB, descsB = feature_detector.detectAndCompute(templateGray, None)
- else:
- print("Feature detector not initialized.")
- return
- # match the features using the chosen method
- if matcher == "BF":
- if detector == "ORB":
- method = cv2.DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMING
- else:
- method = cv2.DescriptorMatcher_BRUTEFORCE
- elif matcher == "FLANN":
- if detector == "SIFT" or detector == "SURF":
- method = cv2.DescriptorMatcher_FLANNBASED
- else:
- print("FLANN matcher is not compatible with ORB detector")
- return
- elif matcher == "KNN":
- if detector == "ORB":
- method = cv2.DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMING
- else:
- method = cv2.DescriptorMatcher_BRUTEFORCE
- else:
- raise ValueError("Invalid matcher method")
- feature_matcher = cv2.DescriptorMatcher_create(method)
- if matcher == "KNN":
- matches = feature_matcher.knnMatch(descsA, descsB, k=2)
- # apply ratio test
- good_matches = []
- for m, n in matches:
- if m.distance < 0.75 * n.distance:
- good_matches.append(m)
- matches = good_matches
- else:
- matches = feature_matcher.match(descsA, descsB, None)
- # sort the matches by their distance (the smaller the distance, the "more similar" the features are)
- matches = sorted(matches, key=lambda x: x.distance)
- # keep only the top matches
- keep = int(len(matches) * keepPercent)
- matches = matches[:keep]
- # if we do not have enough matches to compute a robust homography, return None
- if len(matches) < 4:
- print("Not enough matches to compute a robust homography.")
- return None
- # check to see if we should visualize the matched keypoints
- if debug:
- matchedVis = cv2.drawMatches(image, kpsA, template, kpsB, matches, None)
- matchedVis = imutils.resize(matchedVis, width=1000)
- if use_matplotlib:
- plt.imshow(cv2.cvtColor(matchedVis, cv2.COLOR_BGR2RGB))
- plt.title(
- f"Name: {file_name} [Detector: {detector}, Matcher: {matcher}, Keypoints: {len(matches)}]"
- )
- plt.show()
- plt.close()
- else:
- cv2.imshow(
- f"Matched Keypoints [Detector: {detector}, Matcher: {matcher}, Keypoints: {matches}]",
- matchedVis,
- )
- cv2.waitKey(0)
- # construct the two sets of points for the homography computation
- ptsA = np.float32([kpsA[m.queryIdx].pt for m in matches])
- ptsB = np.float32([kpsB[m.trainIdx].pt for m in matches])
- # compute the homography matrix between the two sets of matched points
- (H, mask) = cv2.findHomography(ptsA, ptsB, method=cv2.RANSAC)
- # use the homography matrix to align the images
- (h, w) = template.shape[:2]
- aligned = cv2.warpPerspective(image, H, (w, h))
- # check if the images were aligned successfully
- if debug and aligned is not None:
- if use_matplotlib:
- plt.imshow(cv2.cvtColor(aligned, cv2.COLOR_BGR2RGB))
- plt.show()
- plt.close()
- else:
- cv2.imshow(
- f"Aligned Image",
- aligned,
- )
- cv2.waitKey(0)
- # return the aligned image
- return aligned
- #image_contour.py
- import cv2
- import numpy as np
- import matplotlib.pyplot as plt
- from PIL import Image
- from ImageProcessor import ImageProcessor
- def image_contour(
- image_path,
- edge_detection_method="Canny",
- filter_type="GaussianBlur",
- filter_radius=4,
- use_matplotlib=False,
- debug=False,
- **kwargs,
- ):
- """
- This function applies an edge detection method to an image and optionally displays the result.
- Parameters:
- image_path (str): The path to the image file.
- edge_detection_method (str): The edge detection method to use ("Canny", "Sobel", or "Laplace").
- filter_type (str): The type of filter to apply before edge detection.
- filter_radius (int): The radius of the filter to apply.
- use_matplotlib (bool): Whether to use Matplotlib to display the image. If False, OpenCV is used.
- debug (bool): If True, displays the image with detected edges.
- **kwargs:
- For "Canny" method:
- canny_threshold1 (int): First threshold for the hysteresis procedure. Default is 100.
- canny_threshold2 (int): Second threshold for the hysteresis procedure. Default is 1500.
- For "Sobel" method:
- dx (int): Order of the derivative x. Default is 1.
- dy (int): Order of the derivative y. Default is 1.
- ksize (int): Size of the extended Sobel kernel; it must be 1, 3, 5, or 7. Default is 5.
- scale (int): Optional scale factor for the computed derivative values. Default is 1.
- delta (int): Optional delta value that is added to the results prior to storing them in dst. Default is 0.
- For "Laplacian" method:
- ddepth (int): Desired depth of the destination image. Default is cv2.CV_64F.
- ksize (int): Aperture size used to compute the second-derivative filters. Default is 1.
- scale (int): Optional scale factor for the computed Laplacian values. Default is 1.
- delta (int): Optional delta value that is added to the results prior to storing them in dst. Default is 0.
- borderType (int): Pixel extrapolation method. Default is cv2.BORDER_DEFAULT.
- Returns:
- edges (ndarray): The image data with edge detection applied.
- """
- # Instantiate the ImageProcessor class
- processor = ImageProcessor(image_path)
- # Apply the selected filter to the image
- processor.blur_filter(filter_type, radius=filter_radius)
- # Convert the PIL image to a NumPy array
- image = np.array(processor.img)
- # Convert the image to grayscale
- gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
- # Apply the selected edge detection method
- if edge_detection_method == "Canny":
- edges = cv2.Canny(
- gray_image,
- kwargs.get("canny_threshold1", 100),
- kwargs.get("canny_threshold2", 1500),
- apertureSize=5,
- L2gradient=True,
- )
- elif edge_detection_method == "Sobel":
- edges = cv2.Sobel(
- gray_image,
- cv2.CV_64F,
- dx=kwargs.get("dx", 1),
- dy=kwargs.get("dy", 1),
- ksize=kwargs.get("ksize", 5),
- scale=kwargs.get("scale", 1),
- delta=kwargs.get("delta", 0),
- )
- elif edge_detection_method == "Laplacian":
- edges = cv2.Laplacian(
- src=gray_image,
- ddepth=cv2.CV_64F,
- ksize=kwargs.get("ksize", 1),
- scale=kwargs.get("scale", 1),
- delta=kwargs.get("delta", 0),
- borderType=kwargs.get("borderType", cv2.BORDER_DEFAULT),
- )
- else:
- print(
- "Invalid edge detection method. Please specify one of the following: 'Canny', 'Laplacian', or 'Sobel'."
- )
- # If debug is True, display the image with detected edges
- if debug:
- if use_matplotlib:
- plt.imshow(edges, cmap="gray")
- plt.title(f"Edge Detection Method: {edge_detection_method}")
- plt.show()
- plt.close()
- else:
- cv2.imshow(f"Edge Detection Method: {edge_detection_method}", edges)
- cv2.waitKey(0)
- cv2.destroyAllWindows()
- # Return the image with edge detection applied
- return edges
- #ImageProcessor
- from PIL import Image, ImageFilter, ImageEnhance, ImageOps
- import numpy as np
- from sklearn.decomposition import PCA
- import matplotlib.pyplot as plt
- import os
- class ImageProcessor:
- def __init__(self, image_path):
- """
- Initialize the ImageProcessor object with the path of the image.
- Parameters:
- image_path (str): The path of the image file.
- """
- self.image_path = image_path
- self.img = None
- self.load_image()
- def load_image(self):
- """
- Load the image from the specified path. If the image file does not exist or cannot be opened,
- an appropriate message will be printed.
- """
- if not os.path.exists(self.image_path):
- print(f"The image file {self.image_path} does not exist.")
- return
- self.img = Image.open(self.image_path)
- if self.img is None:
- print(f"The image file {self.image_path} cannot be opened.")
- return
- def blur_filter(self, filter_type, **kwargs):
- """
- Apply a filter to the image. Available filters are: 'GaussianBlur', 'BoxBlur', 'MedianFilter'.
- Parameters:
- filter_type (str): The type of the filter to be applied. It should be one of the following: 'GaussianBlur', 'BoxBlur', 'MedianFilter'.
- **kwargs: Additional parameters that might be needed for the filters. For 'GaussianBlur' and 'BoxBlur', you can specify 'radius'. For 'MedianFilter', you can specify 'size'.
- """
- if self.img is None:
- print("No image loaded.")
- return
- if filter_type == "GaussianBlur":
- self.img = self.img.filter(
- ImageFilter.GaussianBlur(kwargs.get("radius", 2))
- )
- elif filter_type == "BoxBlur":
- self.img = self.img.filter(ImageFilter.BoxBlur(kwargs.get("radius", 2)))
- elif filter_type == "MedianFilter":
- self.img = self.img.filter(ImageFilter.MedianFilter(kwargs.get("size", 3)))
- def increase_brightness(self, factor=1.2):
- """
- Increase the brightness of the image by a certain factor.
- Parameters:
- factor (float): The factor by which to increase the brightness. Default is 1.2.
- """
- if self.img is None:
- print("No image loaded.")
- return
- enhancer = ImageEnhance.Brightness(self.img)
- self.img = enhancer.enhance(factor)
- def increase_saturation(self, factor=1.2):
- """
- Increase the saturation of the image by a certain factor.
- Parameters:
- factor (float): The factor by which to increase the saturation. Default is 1.2.
- """
- if self.img is None:
- print("No image loaded.")
- return
- enhancer = ImageEnhance.Color(self.img)
- self.img = enhancer.enhance(factor)
- def increase_contrast(self, factor=1.2):
- """
- Increase the contrast of the image by a certain factor.
- Parameters:
- factor (float): The factor by which to increase the contrast. Default is 1.2.
- """
- if self.img is None:
- print("No image loaded.")
- return
- enhancer = ImageEnhance.Contrast(self.img)
- self.img = enhancer.enhance(factor)
- def resize(self, size=None, factor=None, maintain_aspect_ratio=False):
- """
- Resize the image to the specified size or downsample it by a certain factor.
- Parameters:
- 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.
- factor (int): The factor by which to downsample the image. Default is None.
- maintain_aspect_ratio (bool): If True, maintain the aspect ratio when resizing to a specific size. Default is False.
- """
- if self.img is None:
- print("No image loaded.")
- return
- if size is not None:
- if maintain_aspect_ratio:
- width, height = self.img.size
- aspect_ratio = width / height
- if width > height:
- size = (size, int(size / aspect_ratio))
- else:
- size = (int(size * aspect_ratio), size)
- else:
- if isinstance(size, int):
- size = (size, size)
- self.img = self.img.resize(size)
- elif factor is not None:
- width, height = self.img.size
- self.img = self.img.resize((width // factor, height // factor))
- else:
- print("Please provide either size or factor.")
- def rotate(self, angle):
- """
- Rotate the image by a certain angle.
- Parameters:
- angle (float): The angle by which to rotate the image.
- """
- self.img = self.img.rotate(angle)
- def crop(self, box):
- """
- Crop the image to the specified box.
- Parameters:
- box (tuple): The box to which to crop the image.
- """
- self.img = self.img.crop(box)
- def to_grayscale(self):
- """
- Convert the image to grayscale.
- """
- self.img = self.img.convert("L")
- def normalize(self):
- """
- Normalize the image.
- 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).
- """
- self.img = self.img.point(lambda i: i / 255.0)
- def equalize(self):
- """
- Equalize the image.
- 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.
- """
- if self.img is None:
- print("No image loaded.")
- return
- if self.img.mode == "RGBA":
- # Separate the alpha channel
- r, g, b, a = self.img.split()
- # Convert RGB channels to an image and equalize
- rgb_image = Image.merge("RGB", (r, g, b))
- equalized_rgb_image = ImageOps.equalize(rgb_image)
- # Merge equalized RGB channels and alpha channel back into an image
- r, g, b = equalized_rgb_image.split()
- self.img = Image.merge("RGBA", (r, g, b, a))
- else:
- self.img = ImageOps.equalize(self.img)
- def add_noise(self, radius=1.0):
- """
- Add noise to the image.
- 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.
- Parameters:
- radius (float): The radius defining the neighborhood for the noise effect. Default is 1.0.
- """
- if self.img is None:
- print("No image loaded.")
- return
- self.img = self.img.effect_spread(radius)
- def flip(self, direction):
- """
- Flip the image in the specified direction.
- Parameters:
- direction (str): The direction in which to flip the image. It should be either 'horizontal' or 'vertical'.
- """
- if self.img is None:
- print("No image loaded.")
- return
- if direction == "horizontal":
- self.img = self.img.transpose(Image.FLIP_LEFT_RIGHT)
- elif direction == "vertical":
- self.img = self.img.transpose(Image.FLIP_TOP_BOTTOM)
- else:
- print(
- "Invalid direction. Please provide either 'horizontal' or 'vertical'."
- )
- def show_image(self, title="Image", use="Matplotlib"):
- """
- Show the image.
- Parameters:
- title (str): The title of the image. Default is "Image".
- use (str): The library to use for showing the image. Default is "Matplotlib".
- """
- if use == "Matplotlib":
- plt.imshow(self.img)
- plt.title(title)
- plt.show()
- else:
- self.img.show(title=title)
- #image_contour.py
- import cv2
- import numpy as np
- import matplotlib.pyplot as plt
- from PIL import Image
- from ImageProcessor import ImageProcessor
- def image_contour(
- image_path,
- edge_detection_method="Canny",
- filter_type="GaussianBlur",
- filter_radius=4,
- use_matplotlib=False,
- debug=False,
- **kwargs,
- ):
- """
- This function applies an edge detection method to an image and optionally displays the result.
- Parameters:
- image_path (str): The path to the image file.
- edge_detection_method (str): The edge detection method to use ("Canny", "Sobel", or "Laplace").
- filter_type (str): The type of filter to apply before edge detection.
- filter_radius (int): The radius of the filter to apply.
- use_matplotlib (bool): Whether to use Matplotlib to display the image. If False, OpenCV is used.
- debug (bool): If True, displays the image with detected edges.
- **kwargs:
- For "Canny" method:
- canny_threshold1 (int): First threshold for the hysteresis procedure. Default is 100.
- canny_threshold2 (int): Second threshold for the hysteresis procedure. Default is 1500.
- For "Sobel" method:
- dx (int): Order of the derivative x. Default is 1.
- dy (int): Order of the derivative y. Default is 1.
- ksize (int): Size of the extended Sobel kernel; it must be 1, 3, 5, or 7. Default is 5.
- scale (int): Optional scale factor for the computed derivative values. Default is 1.
- delta (int): Optional delta value that is added to the results prior to storing them in dst. Default is 0.
- For "Laplacian" method:
- ddepth (int): Desired depth of the destination image. Default is cv2.CV_64F.
- ksize (int): Aperture size used to compute the second-derivative filters. Default is 1.
- scale (int): Optional scale factor for the computed Laplacian values. Default is 1.
- delta (int): Optional delta value that is added to the results prior to storing them in dst. Default is 0.
- borderType (int): Pixel extrapolation method. Default is cv2.BORDER_DEFAULT.
- Returns:
- edges (ndarray): The image data with edge detection applied.
- """
- # Instantiate the ImageProcessor class
- processor = ImageProcessor(image_path)
- # Apply the selected filter to the image
- processor.blur_filter(filter_type, radius=filter_radius)
- # Convert the PIL image to a NumPy array
- image = np.array(processor.img)
- # Convert the image to grayscale
- gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
- # Apply the selected edge detection method
- if edge_detection_method == "Canny":
- edges = cv2.Canny(
- gray_image,
- kwargs.get("canny_threshold1", 100),
- kwargs.get("canny_threshold2", 1500),
- apertureSize=5,
- L2gradient=True,
- )
- elif edge_detection_method == "Sobel":
- edges = cv2.Sobel(
- gray_image,
- cv2.CV_64F,
- dx=kwargs.get("dx", 1),
- dy=kwargs.get("dy", 1),
- ksize=kwargs.get("ksize", 5),
- scale=kwargs.get("scale", 1),
- delta=kwargs.get("delta", 0),
- )
- elif edge_detection_method == "Laplacian":
- edges = cv2.Laplacian(
- src=gray_image,
- ddepth=cv2.CV_64F,
- ksize=kwargs.get("ksize", 1),
- scale=kwargs.get("scale", 1),
- delta=kwargs.get("delta", 0),
- borderType=kwargs.get("borderType", cv2.BORDER_DEFAULT),
- )
- else:
- print(
- "Invalid edge detection method. Please specify one of the following: 'Canny', 'Laplacian', or 'Sobel'."
- )
- # If debug is True, display the image with detected edges
- if debug:
- if use_matplotlib:
- plt.imshow(edges, cmap="gray")
- plt.title(f"Edge Detection Method: {edge_detection_method}")
- plt.show()
- plt.close()
- else:
- cv2.imshow(f"Edge Detection Method: {edge_detection_method}", edges)
- cv2.waitKey(0)
- cv2.destroyAllWindows()
- # Return the image with edge detection applied
- return edges
- #ImageAnalyzer.py
- import cv2
- import numpy as np
- import matplotlib.pyplot as plt
- from PIL import Image
- class ImageAnalyzer:
- """
- A class used to analyze images based on their histograms.
- ...
- Attributes
- ----------
- img : ndarray
- The image to be analyzed.
- ideal_img : ndarray
- The ideal image to compare with.
- threshold : float
- The threshold for image similarity.
- Methods
- -------
- load_image(input)
- Loads an image from a file path or a Pillow Image object.
- calculate_histogram(img)
- Calculates the histogram of an image.
- compare_histograms(hist1, hist2)
- Compares two histograms using the Bhattacharyya distance.
- compare_images()
- Compares the histograms of two images and prints whether they are similar.
- draw_histograms(img, title)
- Draws the histograms of an image.
- analyze()
- Performs the image analysis.
- """
- def __init__(self, img_input, ideal_img_input):
- """
- Constructs all the necessary attributes for the ImageAnalyzer object.
- Parameters
- ----------
- img_input : str or Image
- The input image file path or Pillow Image object.
- ideal_img_input : str or Image
- The ideal image file path or Pillow Image object.
- """
- self.img = self.load_image(img_input)
- self.ideal_img = self.load_image(ideal_img_input)
- self.threshold = 0.25
- def load_image(self, input):
- """
- Loads an image from a file path or a Pillow Image object.
- Parameters
- ----------
- input : str or Image
- The input image file path or Pillow Image object.
- Returns
- -------
- img : ndarray
- The loaded image.
- """
- if isinstance(input, str): # input is a file path
- return cv2.imread(input)
- elif isinstance(input, Image.Image): # input is a Pillow Image object
- return cv2.cvtColor(np.array(input), cv2.COLOR_RGB2BGR)
- else:
- raise TypeError("Input must be a file path or a Pillow Image object.")
- def calculate_histogram(self, img):
- """
- Calculates the histogram of an image.
- Parameters
- ----------
- img : ndarray
- The image.
- Returns
- -------
- histograms : list
- The histograms of the image channels.
- """
- channels = cv2.split(img)
- histograms = []
- for channel in channels:
- hist = cv2.calcHist([channel], [0], None, [256], [0, 256])
- histograms.append(hist)
- return histograms
- def compare_histograms(self, hist1, hist2):
- """
- Compares two histograms using the Bhattacharyya distance.
- Parameters
- ----------
- hist1 : list
- The first histogram.
- hist2 : list
- The second histogram.
- Returns
- -------
- distance : float
- The Bhattacharyya distance between the two histograms.
- """
- distance = sum(
- [
- cv2.compareHist(h1, h2, cv2.HISTCMP_BHATTACHARYYA)
- for h1, h2 in zip(hist1, hist2)
- ]
- )
- return distance
- def compare_images(self):
- """
- Compares the histograms of two images and prints whether they are similar.
- """
- img_histograms = self.calculate_histogram(self.img)
- ideal_histograms = self.calculate_histogram(self.ideal_img)
- distance = self.compare_histograms(img_histograms, ideal_histograms)
- if distance < self.threshold:
- print("The image is similar to the ideal image")
- else:
- print("The image is not similar to the ideal image")
- def draw_histograms(self, img, title):
- """
- Draws the histograms of an image.
- Parameters
- ----------
- img : ndarray
- The image.
- title : str
- The title of the plot.
- """
- vals = img.mean(axis=2).flatten()
- counts, bins = np.histogram(vals, range(257))
- channels = cv2.split(img)
- colors = ("b", "g", "r")
- plt.bar(
- bins[:-1] - 0.5,
- counts,
- width=1,
- color="yellow",
- edgecolor="none",
- alpha=0.5,
- label="Luminosity",
- )
- for channel, color in zip(channels, colors):
- hist = cv2.calcHist([channel], [0], None, [256], [0, 256])
- plt.plot(hist, color=color, label=color)
- plt.title(title)
- plt.xlim([-0.5, 255.5])
- plt.legend()
- plt.show()
- def analyze(self):
- """
- Performs the image analysis by comparing the images and drawing their histograms.
- """
- self.compare_images()
- self.draw_histograms(self.img, "img")
- self.draw_histograms(self.ideal_img, "ideal_img")
- [ImageCluster.py
- # Librerie per l'elaborazione delle immagini
- from PIL import Image
- import numpy as np
- # Librerie per il machine learning
- from sklearn.cluster import KMeans
- from scipy.spatial import distance
- # Librerie per la visualizzazione dei dati
- import matplotlib.pyplot as plt
- import matplotlib.cm as cm
- from matplotlib.ticker import AutoMinorLocator, MaxNLocator
- from matplotlib.colors import ListedColormap
- import matplotlib.patches as mpatches
- # Altre librerie
- from collections import Counter
- import os
- # aggiungi una caratteristica tipo nome file (Senza estensione) in modo da usarlo per salvare tutti i grafici
- class ImageCluster:
- """
- This class provides methods to perform color clustering on an image.
- The image can be provided as a file path or as a PIL.Image object.
- """
- def __init__(self, image_input):
- """
- Initializes the ImageCluster object.
- If image_input is a string, it is treated as a file path and the image is loaded from that path.
- If image_input is an instance of PIL.Image, it is used directly.
- """
- if isinstance(image_input, str):
- self.image_path = image_input
- self.filename = os.path.splitext(os.path.basename(self.image_path))[0]
- self.img = Image.open(self.image_path).convert("RGBA")
- elif isinstance(image_input, Image.Image):
- self.img = image_input.convert("RGBA")
- self.filename = "image"
- else:
- raise TypeError(
- "image_input deve essere un percorso dell'immagine o un'istanza di PIL.Image"
- )
- self.n_clusters = None
- self.initial_clusters = None
- self.img_array = np.array(self.img)
- self.data = self.img_array.reshape(-1, 4)
- self.data = self.data.astype(float)
- self.removeTransparent = False
- self.labels_full = None
- self.mask = None
- self.clustered_img = None
- self.cluster_infos = None
- def remove_transparent(self, alpha_threshold=250):
- """
- Removes transparent pixels from the image.
- A pixel is considered transparent if its alpha value is less than alpha_threshold.
- """
- transparent_pixels = self.data[:, 3] <= alpha_threshold
- self.data[transparent_pixels] = np.nan
- self.removeTransparent = True
- def filter_alpha(self):
- """
- Returns a boolean mask indicating which pixels in the image are not transparent.
- """
- return ~np.isnan(self.data[:, 3])
- def cluster(
- self, n_clusters=None, initial_clusters=None, merge_similar=False, threshold=10
- ):
- """
- Performs color clustering on the image.
- If initial_clusters is provided, it is used as initialization for the KMeans algorithm.
- Otherwise, if n_clusters is provided, it is used to determine the number of clusters.
- If merge_similar is True, clusters with similar colors are merged.
- The threshold for determining whether two colors are similar is given by threshold.
- """
- self.initial_clusters = initial_clusters
- if initial_clusters is not None:
- self.n_clusters = self.initial_clusters.shape[0]
- else:
- if n_clusters is not None:
- self.n_clusters = n_clusters
- else:
- print("Error, choice cluster number n_clusters")
- mask = self.filter_alpha()
- self.mask = mask
- data_no_nan = self.data[mask]
- if self.initial_clusters is not None:
- kmeans = KMeans(
- n_clusters=self.n_clusters, init=self.initial_clusters, n_init=10
- )
- else:
- kmeans = KMeans(n_clusters=self.n_clusters, random_state=0)
- self.labels = kmeans.fit_predict(data_no_nan[:, :3]) # Ignora la colonna alpha
- self.center_colors = kmeans.cluster_centers_
- self.labels_full = np.full(mask.shape[0], -1)
- self.labels_full[mask] = self.labels
- if merge_similar:
- while True:
- # Calcola la distanza euclidea tra i colori dei centri dei cluster
- distances = distance.cdist(
- self.center_colors, self.center_colors, "euclidean"
- )
- # Trova la distanza minima che non sia sulla diagonale
- min_distance = np.min(
- distances + np.eye(distances.shape[0]) * distances.max()
- )
- if min_distance >= threshold:
- break
- else:
- self.n_clusters -= 1
- kmeans = KMeans(n_clusters=self.n_clusters, random_state=0)
- self.labels = kmeans.fit_predict(
- data_no_nan[:, :3]
- ) # Ignora la colonna alpha
- self.center_colors = kmeans.cluster_centers_
- self.labels_full = np.full(mask.shape[0], -1)
- self.labels_full[mask] = self.labels
- def create_clustered_image(self):
- """
- Creates an image where each pixel is replaced with the color of its cluster.
- """
- self.clustered_img = np.zeros_like(self.img_array)
- for i in range(self.img_array.shape[0]):
- for j in range(self.img_array.shape[1]):
- if self.labels_full[i * self.img_array.shape[1] + j] != -1:
- self.clustered_img[i, j, :3] = self.center_colors[
- self.labels_full[i * self.img_array.shape[1] + j]
- ]
- self.clustered_img[i, j, 3] = self.data[
- i * self.img_array.shape[1] + j, 3
- ] # Mantieni il valore alfa originale
- else:
- self.clustered_img[i, j] = [
- 255,
- 255,
- 255,
- 0,
- ] # white or transparent
- def create_clustered_image_with_ids(self):
- """
- Creates an image where each pixel is replaced with the ID of its cluster.
- """
- # Inizializza un array bidimensionale con la stessa forma di self.img_array
- self.clustered_img_with_ids = np.zeros(
- (self.img_array.shape[0], self.img_array.shape[1])
- )
- for i in range(self.img_array.shape[0]):
- for j in range(self.img_array.shape[1]):
- if self.labels_full[i * self.img_array.shape[1] + j] != -1:
- # Rimpiazza il colore con l'ID del cluster
- self.clustered_img_with_ids[i, j] = self.labels_full[
- i * self.img_array.shape[1] + j
- ]
- else:
- self.clustered_img_with_ids[i, j] = self.n_clusters + 1
- def extract_cluster_info(self):
- """
- Extracts information about the clusters, such as the color of the centroid, the number of pixels, and the percentage of total pixels.
- """
- counter = Counter(self.labels)
- clusters_sorted = sorted(counter.items(), key=lambda x: x[1], reverse=True)
- cluster_info = {}
- total_pixels = sum(counter.values())
- for i, (cluster, count) in enumerate(clusters_sorted):
- cluster_info[i] = {
- "color": self.center_colors[cluster],
- "pixel_count": count,
- "total_pixel_percentage": (count / total_pixels) * 100,
- "original_position": cluster,
- }
- cluster_info = dict(
- sorted(
- cluster_info.items(),
- key=lambda item: item[1]["pixel_count"],
- reverse=True,
- )
- )
- self.cluster_infos = cluster_info
- self.total_pixels = total_pixels
- def calculate_brightness(self, color):
- """
- Calculates the brightness of a color.
- Brightness is defined as the average of the RGB values.
- """
- # Calcola la luminosità come la media dei valori RGB
- return sum(color) / (3 * 255)
- def plot_original_image(self, ax=None, max_size=(1024, 1024)):
- """
- Displays the original image.
- If ax is provided, the image is displayed on that subplot.
- Otherwise, a new subplot is created.
- The image is resized to max_size to avoid using too much memory.
- """
- # Riduci la risoluzione dell'immagine
- img = self.img.copy()
- img.thumbnail(max_size, Image.LANCZOS)
- if ax is None:
- ax = plt.gca()
- ax.imshow(np.array(img))
- ax.set_title("Original Image")
- ax.axis("off")
- def plot_clustered_image(self, ax=None, max_size=(1024, 1024)):
- """
- Displays the clustered image.
- If ax is provided, the image is displayed on that subplot.
- Otherwise, a new subplot is created.
- The image is resized to max_size to avoid using too much memory.
- """
- # Pre-calcola l'immagine raggruppata
- if self.clustered_img is None:
- self.create_clustered_image()
- # Riduci la risoluzione dell'immagine raggruppata
- img = Image.fromarray(self.clustered_img).convert("RGBA")
- img.thumbnail(max_size, Image.LANCZOS)
- if ax is None:
- ax = plt.gca()
- ax.imshow(np.array(img))
- ax.set_title("Clustered Image ({} clusters)".format(self.n_clusters))
- ax.axis("off")
- def plot_clustered_image_high_contrast(
- self, style="jet", show_percentage=True, dpi=100
- ):
- """
- Displays the clustered image with high contrast between the cluster colors.
- The style parameter determines the colormap used.
- If show_percentage is True, the percentage of pixels in each cluster is displayed in the legend.
- """
- # Prima assicurati di aver chiamato la funzione create_clustered_image_with_ids
- self.create_clustered_image_with_ids()
- # Crea una nuova figura con i DPI specificati
- fig, ax = plt.subplots(dpi=dpi)
- # Visualizza l'immagine
- im = ax.imshow(self.clustered_img_with_ids, cmap=style)
- # Crea una legenda con un rettangolo colorato per ogni etichetta di cluster
- colors = [
- im.cmap(im.norm(self.cluster_infos[i]["original_position"]))
- for i in range(self.n_clusters)
- ]
- if show_percentage:
- labels = [
- f"Cluster {self.cluster_infos[i]['original_position']} ({self.cluster_infos[i]['total_pixel_percentage']:.2f}%)"
- for i in range(self.n_clusters)
- if i in self.cluster_infos
- ]
- else:
- labels = [
- f"Cluster {self.cluster_infos[i]['original_position']}"
- for i in range(self.n_clusters)
- ]
- patches = [
- mpatches.Patch(color=colors[i], label=labels[i]) for i in range(len(colors))
- ]
- plt.legend(
- handles=patches,
- bbox_to_anchor=(1.05, 1),
- loc=2,
- borderaxespad=0.0,
- title="Legend",
- )
- ax.set_title(
- "Clustered Image with High Contrast ({} clusters)".format(self.n_clusters)
- )
- ax.axis("off")
- # Mostra la figura
- plt.show()
- def plot_cluster_pie(self, ax=None, dpi=100):
- """
- Displays a pie chart showing the distribution of pixels among the clusters.
- If ax is provided, the chart is displayed on that subplot.
- Otherwise, a new subplot is created.
- """
- if ax is None:
- fig, ax = plt.subplots(dpi=dpi)
- labels = [
- f"Cluster {self.cluster_infos[i]['original_position']}"
- for i in range(self.n_clusters)
- if i in self.cluster_infos
- ]
- sizes = [
- self.cluster_infos[i]["pixel_count"]
- for i in range(self.n_clusters)
- if i in self.cluster_infos
- ]
- colors = [
- self.cluster_infos[i]["color"] / 255
- for i in range(self.n_clusters)
- if i in self.cluster_infos
- ]
- wedges, text_labels, text_percentages = ax.pie(
- sizes, labels=labels, colors=colors, startangle=90, autopct="%1.1f%%"
- )
- for i in range(len(wedges)):
- color = "white" if self.calculate_brightness(colors[i]) < 0.5 else "black"
- text_labels[i].set_color(color)
- text_percentages[i].set_color(color)
- ax.legend(
- wedges,
- labels,
- title="Clusters",
- loc="center left",
- bbox_to_anchor=(1, 0, 0.5, 1),
- )
- ax.axis("equal")
- ax.set_title("PieChart ({} clusters)".format(self.n_clusters))
- def plot_cluster_bar(self, ax=None, dpi=100):
- """
- Displays a bar chart showing the distribution of pixels among the clusters.
- If ax is provided, the chart is displayed on that subplot.
- Otherwise, a new subplot is created.
- """
- if ax is None:
- fig, ax = plt.subplots(dpi=dpi)
- labels = [f"Cluster {i}" for i in self.cluster_infos.keys()]
- percentages = [
- info["total_pixel_percentage"] for info in self.cluster_infos.values()
- ]
- pixel_counts = [info["pixel_count"] for info in self.cluster_infos.values()]
- colors = [info["color"] / 255 for info in self.cluster_infos.values()]
- bars = ax.bar(labels, percentages, color=colors)
- for bar, pixel_count in zip(bars, pixel_counts):
- ax.text(
- bar.get_x() + bar.get_width() / 2,
- bar.get_height(),
- str(pixel_count),
- ha="center",
- va="bottom",
- )
- ax.set_xlabel("Cluster")
- ax.set_ylabel("Percentage")
- def plot_cumulative_barchart(self, ax=None, dpi=100):
- """
- Displays a cumulative bar chart showing the distribution of pixels among the clusters.
- If ax is provided, the chart is displayed on that subplot.
- Otherwise, a new subplot is created.
- """
- if ax is None:
- fig, ax = plt.subplots(dpi=dpi)
- bottom = 0
- for i, info in self.cluster_infos.items():
- color = info["color"] / 255
- percentage = info["total_pixel_percentage"]
- pixel_count = info["pixel_count"]
- ax.bar("Cluster", height=percentage, color=color, bottom=bottom)
- brightness = self.calculate_brightness(color)
- text_color = "white" if brightness < 0.75 else "black"
- ax.text(
- "Cluster",
- bottom + percentage / 2,
- str(pixel_count),
- ha="center",
- va="center",
- color=text_color,
- )
- ax.yaxis.set_minor_locator(AutoMinorLocator())
- bottom += percentage
- ax.spines["top"].set_visible(False)
- ax.spines["right"].set_visible(False)
- ax.spines["bottom"].set_visible(False)
- ax.spines["left"].set_visible(True)
- ax.axes.xaxis.set_visible(False)
- def plot_images(self, max_size=(1024, 1024)):
- """
- Displays the original image, the clustered image, and the high contrast clustered image side by side.
- """
- fig, axs = plt.subplots(1, 3, figsize=(20, 5))
- self.plot_original_image(ax=axs[0], max_size=max_size)
- self.plot_clustered_image(ax=axs[1], max_size=max_size)
- self.plot_clustered_image_high_contrast(ax=axs[2])
- def plot_image_with_grid(
- self, grid_size=50, color="white", max_size=(1024, 1024), dpi=100
- ):
- """
- Displays the original image with a grid overlaid.
- The grid size is determined by grid_size.
- The grid color is determined by color.
- The image is resized to max_size to avoid using too much memory.
- """
- fig, ax = plt.subplots(dpi=dpi)
- # Riduci la risoluzione dell'immagine originale
- img = self.img.copy()
- img.thumbnail(max_size, Image.LANCZOS)
- # Mostra l'immagine originale
- ax.imshow(np.array(img))
- # Aggiungi la griglia
- ax.set_xticks(np.arange(-0.5, img.size[0], grid_size), minor=True)
- ax.set_yticks(np.arange(-0.5, img.size[1], grid_size), minor=True)
- ax.grid(which="minor", color=color, linestyle="-", linewidth=2)
- # Imposta il titolo e nasconde gli assi
- ax.set_title("Original Image with Grid")
- ax.axis("on")
- plt.show()
- def save_plots(self):
- """
- Saves all the plots in a directory named "output/{self.filename}".
- If the directory does not exist, it is created.
- """
- # Crea la directory se non esiste
- if not os.path.exists(f"output/{self.filename}"):
- os.makedirs(f"output/{self.filename}")
- self.plot_original_image()
- plt.savefig(f"output/{self.filename}/{self.filename}.png")
- self.plot_clustered_image()
- plt.savefig(f"output/{self.filename}/{self.filename}_cluster_image.png")
- self.plot_cluster_pie()
- plt.savefig(f"output/{self.filename}/{self.filename}_piechart.png")
- self.plot_clustered_image_high_contrast()
- plt.savefig(f"output/{self.filename}/{self.filename}_high_contrast.png")
- #ImagePRocessor
- from PIL import Image, ImageFilter, ImageEnhance, ImageOps
- import numpy as np
- from sklearn.decomposition import PCA
- import matplotlib.pyplot as plt
- import os
- class ImageProcessor:
- def __init__(self, image_path):
- """
- Initialize the ImageProcessor object with the path of the image.
- Parameters:
- image_path (str): The path of the image file.
- """
- self.image_path = image_path
- self.img = None
- self.load_image()
- def load_image(self):
- """
- Load the image from the specified path. If the image file does not exist or cannot be opened,
- an appropriate message will be printed.
- """
- if not os.path.exists(self.image_path):
- print(f"The image file {self.image_path} does not exist.")
- return
- self.img = Image.open(self.image_path)
- if self.img is None:
- print(f"The image file {self.image_path} cannot be opened.")
- return
- def blur_filter(self, filter_type, **kwargs):
- """
- Apply a filter to the image. Available filters are: 'GaussianBlur', 'BoxBlur', 'MedianFilter'.
- Parameters:
- filter_type (str): The type of the filter to be applied. It should be one of the following: 'GaussianBlur', 'BoxBlur', 'MedianFilter'.
- **kwargs: Additional parameters that might be needed for the filters. For 'GaussianBlur' and 'BoxBlur', you can specify 'radius'. For 'MedianFilter', you can specify 'size'.
- """
- if self.img is None:
- print("No image loaded.")
- return
- if filter_type == "GaussianBlur":
- self.img = self.img.filter(
- ImageFilter.GaussianBlur(kwargs.get("radius", 2))
- )
- elif filter_type == "BoxBlur":
- self.img = self.img.filter(ImageFilter.BoxBlur(kwargs.get("radius", 2)))
- elif filter_type == "MedianFilter":
- self.img = self.img.filter(ImageFilter.MedianFilter(kwargs.get("size", 3)))
- def increase_brightness(self, factor=1.2):
- """
- Increase the brightness of the image by a certain factor.
- Parameters:
- factor (float): The factor by which to increase the brightness. Default is 1.2.
- """
- if self.img is None:
- print("No image loaded.")
- return
- enhancer = ImageEnhance.Brightness(self.img)
- self.img = enhancer.enhance(factor)
- def increase_saturation(self, factor=1.2):
- """
- Increase the saturation of the image by a certain factor.
- Parameters:
- factor (float): The factor by which to increase the saturation. Default is 1.2.
- """
- if self.img is None:
- print("No image loaded.")
- return
- enhancer = ImageEnhance.Color(self.img)
- self.img = enhancer.enhance(factor)
- def increase_contrast(self, factor=1.2):
- """
- Increase the contrast of the image by a certain factor.
- Parameters:
- factor (float): The factor by which to increase the contrast. Default is 1.2.
- """
- if self.img is None:
- print("No image loaded.")
- return
- enhancer = ImageEnhance.Contrast(self.img)
- self.img = enhancer.enhance(factor)
- def resize(self, size=None, factor=None, maintain_aspect_ratio=False):
- """
- Resize the image to the specified size or downsample it by a certain factor.
- Parameters:
- 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.
- factor (int): The factor by which to downsample the image. Default is None.
- maintain_aspect_ratio (bool): If True, maintain the aspect ratio when resizing to a specific size. Default is False.
- """
- if self.img is None:
- print("No image loaded.")
- return
- if size is not None:
- if maintain_aspect_ratio:
- width, height = self.img.size
- aspect_ratio = width / height
- if width > height:
- size = (size, int(size / aspect_ratio))
- else:
- size = (int(size * aspect_ratio), size)
- else:
- if isinstance(size, int):
- size = (size, size)
- self.img = self.img.resize(size)
- elif factor is not None:
- width, height = self.img.size
- self.img = self.img.resize((width // factor, height // factor))
- else:
- print("Please provide either size or factor.")
- def rotate(self, angle):
- """
- Rotate the image by a certain angle.
- Parameters:
- angle (float): The angle by which to rotate the image.
- """
- self.img = self.img.rotate(angle)
- def crop(self, box):
- """
- Crop the image to the specified box.
- Parameters:
- box (tuple): The box to which to crop the image.
- """
- self.img = self.img.crop(box)
- def to_grayscale(self):
- """
- Convert the image to grayscale.
- """
- self.img = self.img.convert("L")
- def normalize(self):
- """
- Normalize the image.
- 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).
- """
- self.img = self.img.point(lambda i: i / 255.0)
- def equalize(self):
- """
- Equalize the image.
- 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.
- """
- if self.img is None:
- print("No image loaded.")
- return
- if self.img.mode == "RGBA":
- # Separate the alpha channel
- r, g, b, a = self.img.split()
- # Convert RGB channels to an image and equalize
- rgb_image = Image.merge("RGB", (r, g, b))
- equalized_rgb_image = ImageOps.equalize(rgb_image)
- # Merge equalized RGB channels and alpha channel back into an image
- r, g, b = equalized_rgb_image.split()
- self.img = Image.merge("RGBA", (r, g, b, a))
- else:
- self.img = ImageOps.equalize(self.img)
- def add_noise(self, radius=1.0):
- """
- Add noise to the image.
- 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.
- Parameters:
- radius (float): The radius defining the neighborhood for the noise effect. Default is 1.0.
- """
- if self.img is None:
- print("No image loaded.")
- return
- self.img = self.img.effect_spread(radius)
- def flip(self, direction):
- """
- Flip the image in the specified direction.
- Parameters:
- direction (str): The direction in which to flip the image. It should be either 'horizontal' or 'vertical'.
- """
- if self.img is None:
- print("No image loaded.")
- return
- if direction == "horizontal":
- self.img = self.img.transpose(Image.FLIP_LEFT_RIGHT)
- elif direction == "vertical":
- self.img = self.img.transpose(Image.FLIP_TOP_BOTTOM)
- else:
- print(
- "Invalid direction. Please provide either 'horizontal' or 'vertical'."
- )
- def show_image(self, title="Image", use="Matplotlib"):
- """
- Show the image.
- Parameters:
- title (str): The title of the image. Default is "Image".
- use (str): The library to use for showing the image. Default is "Matplotlib".
- """
- if use == "Matplotlib":
- plt.imshow(self.img)
- plt.title(title)
- plt.show()
- else:
- self.img.show(title=title)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement