예제 #1
0
class Smile(Transform):

    arguments = {
        "scale": Number(default=1.2),
        "min_neighbors": Number(min_value=0, only_integer=True, default=20),
    }

    outputs = {
        "rectangles":
        List(
            List(List(Number(min_value=0, only_integer=True), length=2),
                 length=2)),
    }

    def process(self, image, **kwargs):
        faces = Faces().apply(image)
        cascade_file = get_resource("haar-smile-cascade",
                                    "haarcascade_smile.xml")
        rectangles = []
        for face in faces["rectangles"]:
            face_image = Crop(rectangle=face).apply(image)
            smile = CascadeDetector(cascade=str(cascade_file),
                                    **kwargs).apply(face_image)["rectangles"]
            if smile:
                adjusted = []
                for i in range(len(smile[0])):
                    adjusted.append((smile[0][i][0] + face[0][0],
                                     smile[0][i][1] + face[0][1]))
                rectangles.append(adjusted)
        return {"rectangles": rectangles}
예제 #2
0
class Scan(Transform):
    """
    Scan is a transform that scans and decodes all QR codes and barcodes in a image. The \
    transform returns the number of detections, the data encoded on each code and their bounding \
    boxes.
    """

    outputs = {
        "detections":
        Number(min_value=0, only_integer=True),
        "data":
        List(Type(str)),
        "rectangles":
        List(
            List(List(Number(min_value=0, only_integer=True), length=2),
                 length=2)),
    }

    def process(self, image, **kwargs):
        data = []
        rectangles = []
        decoded = pyzbar.decode(image)

        for code in decoded:
            data.append(code.data.decode("utf-8"))
            (x, y, width, height) = code.rect
            rectangles.append([(x, y), (x + width, y + height)])

        return {
            "detections": len(decoded),
            "data": data,
            "rectangles": rectangles
        }
예제 #3
0
class CascadeDetector(Transform):

    arguments = {
        "cascade": File(),
        "scale": Number(default=1.1),
        "min_neighbors": Number(min_value=0, only_integer=True, default=3),
        "min_size": Number(min_value=0, default="auto"),
        "max_size": Number(min_value=0, default="auto"),
    }

    outputs = {
        "rectangles":
        List(
            List(List(Number(min_value=0, only_integer=True), length=2),
                 length=2)),
    }

    def process(self, image, **kwargs):
        cascade = cv2.CascadeClassifier(kwargs["cascade"])
        gray = GrayScale().apply(image)
        detections = cascade.detectMultiScale(
            gray,
            scaleFactor=kwargs["scale"],
            minNeighbors=kwargs["min_neighbors"],
            minSize=kwargs["min_size"]
            if kwargs["min_size"] != "auto" else None,
            maxSize=kwargs["max_size"]
            if kwargs["max_size"] != "auto" else None,
        )

        rectangles = []
        for x, y, w, h in detections:
            rectangles.append([(x, y), (x + w, y + h)])

        return {"rectangles": rectangles}
예제 #4
0
class Morphology(Transform):
    """
    Morphology is a transform that applies different morphological operation to images. Available \
    operations are:

    \t**∙ opening** - Opening is an Erosion followed by a dilation\n
    \t**∙ closing** - Closing is the reverse of Opening\n
    \t**∙ tophat** - Difference between the image and it's opening\n
    \t**∙ blackhat** - Difference between the image and it's closing\n

    :param size: Kernel size, defaults to 5
    :type size: :class:`int`, optional
    :param iterations: Number of iterations, defaults to 1
    :type iterations: :class:`int`, optional
    """

    arguments = {
        "size": Number(min_value=1,
                       only_integer=True,
                       only_odd=True,
                       default=5),
        "iterations": Number(min_value=1, only_integer=True, default=1),
    }

    methods = ["opening", "closing", "tophat", "blackhat"]
    default_method = "opening"

    def process(self, image, **kwargs):
        kernel = np.ones((kwargs["size"], kwargs["size"]), np.uint8)
        return cv2.morphologyEx(
            image,
            morp_methods[kwargs["method"]],
            kernel,
            iterations=kwargs["iterations"],
        )
예제 #5
0
class Eyes(Transform):

    arguments = {
        "scale": Number(default=1.1),
        "min_neighbors": Number(min_value=0, only_integer=True, default=3),
    }

    outputs = {
        "rectangles":
        List(
            List(List(Number(min_value=0, only_integer=True), length=2),
                 length=2)),
    }

    def process(self, image, **kwargs):
        cascade_file = get_resource("haar-eye-cascade", "haarcascade_eye.xml")

        rectangles = []
        for face in Faces().apply(image)["rectangles"]:
            face_image = Crop(rectangle=face).apply(image)
            eyes = CascadeDetector(cascade=str(cascade_file),
                                   **kwargs).apply(face_image)["rectangles"]
            for eye in eyes:
                adjusted = []
                for i in range(len(eye)):
                    adjusted.append(
                        (eye[i][0] + face[0][0], eye[i][1] + face[0][1]))
                rectangles.append(adjusted)

        return {"rectangles": rectangles}
예제 #6
0
class Sharpness(Transform):
    """
    Sharpness is a transform that measures how sharpen an image is. Images are classified as \
    sharpen when above a certain value of sharpness given by the threshold. \
    Currently supported interpolation methods:

    \t**∙ laplace** - Uses laplacian to calculate Sharpness\n
    \t**∙ fft** - Uses Fast Fourier Transform to calculate Sharpness\n

    :param threshold: Threshold to classify images as sharpen, defaults to 100 (for fft this \
    should be arround 10)
    :type threshold: :class:`int`/:class:`float`, optional
    :param size: Radius around the centerpoint to zero out the FFT shift
    :type size: :class:`int`, optional
    """

    methods = {
        "laplace": {
            "arguments": ["threshold"]
        },
        "fft": {
            "arguments": ["size", "threshold"]
        },
    }
    default_method = "laplace"

    arguments = {
        "threshold": Number(min_value=0, default=100),
        "size": Number(min_value=0, only_integer=True, default=60),
    }

    outputs = {"sharpness": Number(), "sharpen": Type(bool)}

    def process(self, image, **kwargs):
        grayscale = GrayScale().apply(image)

        if kwargs["method"] == "laplace":
            sharpness = (easycv.transforms.edges.Gradient(
                method="laplace").apply(grayscale).var())
        else:
            h, w = grayscale.shape
            centerx, centery = (int(w / 2.0), int(h / 2.0))
            fft = np.fft.fft2(grayscale)
            fft_shift = np.fft.fftshift(fft)
            size = kwargs["size"]
            fft_shift[centery - size:centery + size,
                      centerx - size:centerx + size] = 0
            fft_shift = np.fft.ifftshift(fft_shift)
            recon = np.fft.ifft2(fft_shift)
            sharpness = np.mean(20 * np.log(np.abs(recon)))

        return {
            "sharpness": sharpness,
            "sharpen": sharpness >= kwargs["threshold"]
        }
예제 #7
0
파일: color.py 프로젝트: easycv/easycv
class Quantitization(Transform):
    """
    Quantitization is a Transform that reduces the number of colors to the on give

    :param clusters: Number of colors that the image will have
    :type clusters: :class:`int`, required
    """

    arguments = {
        "clusters": Number(min_value=1, only_integer=True),
    }

    def process(self, image, **kwargs):
        (h, w) = image.shape[:2]
        image = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
        image = image.reshape((image.shape[0] * image.shape[1], 3))

        clt = MiniBatchKMeans(n_clusters=kwargs["clusters"])
        labels = clt.fit_predict(image)

        quant = clt.cluster_centers_.astype("uint8")[labels]
        quant = quant.reshape((h, w, 3))
        quant = cv2.cvtColor(quant, cv2.COLOR_LAB2BGR)

        return quant
예제 #8
0
파일: color.py 프로젝트: easycv/easycv
class ColorPick(Transform):
    """
    ColorPick is a transform that returns the color of a selected point or the \
    average color of a selected rectangle. Returns the color in RGB.

    :param method: Method to be used, defaults to "point"
    :type method: :class:`str`, optional
    """

    methods = ["point", "rectangle"]
    default_method = "point"

    outputs = {
        "color":
        List(Number(min_value=0, max_value=255, only_integer=True), length=3)
    }

    def process(self, image, **kwargs):
        if kwargs["method"] == "point":
            point = Select(method="point", n=1).apply(image)["points"][0]
            return {"color": list(image[point[1]][point[0]][::-1])}
        if kwargs["method"] == "rectangle":
            rectangle = Select(method="rectangle").apply(image)["rectangle"]
            cropped = Crop(rectangle=rectangle).apply(image)
            return {
                "color":
                list(cropped.mean(axis=(1, 0)).round().astype("uint8"))[::-1]
            }
예제 #9
0
class Inpaint(Transform):
    """
    Inpaint applies an inpainting technique to an image.

    :param radius: Inpainting radius
    :type radius: :class:`int`
    :param mask: Mask to apply inpaint
    :type mask: :class:`Image`
    """

    methods = {
        "telea": {"arguments": ["radius", "mask"]},
        "ns": {"arguments": ["radius", "mask"]},
    }
    default_method = "telea"

    arguments = {
        "radius": Number(only_integer=True, min_value=0, default=3),
        "mask": Image(),
    }

    def process(self, image, **kwargs):
        flag = cv2.INPAINT_TELEA if kwargs["method"] == "telea" else cv2.INPAINT_NS

        return cv2.inpaint(image, kwargs["mask"].array, kwargs["radius"], flags=flag)
예제 #10
0
class Mask(Transform):
    """
    Mask applies a mask to an image.

    :param mask: Mask to apply
    :type brush: :class:`Image`
    :param inverse: Inverts mask
    :type inverse: :class:`bool`
    :param fill_color: Color to fill
    :type fill_color: :class:`List`
    """

    arguments = {
        "mask": Image(),
        "inverse": Type(bool, default=False),
        "fill_color": List(
            Number(only_integer=True, min_value=0, max_value=255),
            length=3,
            default=(0, 0, 0),
        ),
    }

    def process(self, image, **kwargs):

        if kwargs["inverse"]:
            mask = cv2.bitwise_not(kwargs["mask"].array)
        else:
            mask = kwargs["mask"].array

        image = cv2.bitwise_and(image, image, mask=mask)
        image[mask == 0] = kwargs["fill_color"]

        return image
예제 #11
0
class Circles(Transform):
    """
    Circles is a transform that can detect where there are circles in an image using the hough
    space

    :param size: Kernel size for media blur, defaults to 1
    :type size: :class:`int`, optional
    :param dp: Inverse ratio of the accumulator resolution to the image resolution, defaults to 1.
    :type dp: :class:`int`, optional
    :param min_dist: Minimal distance between centers of circles, defaults to 1.
    :type min_dist: :class:`int`/:class:`float`, optional
    :param min_radius: Minimum radius of a circle, defaults to 0
    :type min_radius: :class:`int`/:class:`float`, optional
    :param max_radius: Maximum radius of a circle, defaults to 0
    :type max_radius: :class:`int`/:class:`float`, optional
    :param high: High canny threshold, defaults to 200
    :type high: :class:`int`, optional
    :param threshold: Threshold of votes, defaults to 200
    :type threshold: :class:`int`, optional
    """

    arguments = {
        "size": Number(min_value=1,
                       only_integer=True,
                       only_odd=True,
                       default=1),
        "dp": Number(min_value=1, only_integer=True, default=1),
        "min_dist": Number(min_value=0, default=1),
        "min_radius": Number(min_value=0, default=0),
        "max_radius": Number(min_value=0, default=0),
        "high": Number(min_value=0, only_integer=True, default=200),
        "threshold": Number(min_value=0, only_integer=True, default=200),
    }

    outputs = {
        "circles": List(List(Number(min_value=0, only_integer=True), length=3))
    }

    def process(self, image, **kwargs):
        blur = easycv.transforms.filter.Blur(method="median",
                                             size=kwargs["size"]).apply(image)
        gray = GrayScale().apply(blur)
        circles = cv2.HoughCircles(
            gray,
            cv2.HOUGH_GRADIENT,
            kwargs["dp"],
            kwargs["min_dist"],
            param1=kwargs["high"],
            param2=kwargs["threshold"],
            minRadius=kwargs["min_radius"],
            maxRadius=kwargs["max_radius"],
        )
        return {"circles": circles[0]}
예제 #12
0
파일: color.py 프로젝트: easycv/easycv
class Cartoon(Transform):
    """
    Cartoon is a transform that creates a stylized / cartoonized image.

    :param smoothing: Determines the amount of smoothing, defaults to 60.
    :type smoothing: :class:`float`, optional
    :param region_size: Determines the size of regions of constant color, defaults to 0.45.
    :type region_size: :class:`float`, optional
    """

    arguments = {
        "smoothing": Number(min_value=0, max_value=200, default=60),
        "region_size": Number(min_value=0, max_value=1, default=0.45),
    }

    def process(self, image, **kwargs):
        return cv2.stylization(image,
                               sigma_s=kwargs["smoothing"],
                               sigma_r=kwargs["region_size"])
예제 #13
0
class Faces(Transform):

    arguments = {
        "scale": Number(default=1.3),
        "min_neighbors": Number(min_value=0, only_integer=True, default=5),
    }

    outputs = {
        "rectangles":
        List(
            List(List(Number(min_value=0, only_integer=True), length=2),
                 length=2)),
    }

    def process(self, image, **kwargs):
        cascade_file = get_resource("haar-face-cascade",
                                    "haarcascade_frontalface_default.xml")
        return CascadeDetector(cascade=str(cascade_file),
                               **kwargs).apply(image)
예제 #14
0
class Sharpen(Transform):
    """
    Sharpen is a transform that sharpens an image.

    :param sigma: Kernel sigma, defaults to 1
    :type sigma: :class:`float`, optional
    :param amount: Amount to sharpen, defaults to 1
    :type amount: :class:`float`, optional
    :param multichannel: `True` if diferent processing for each color layer `False` otherwise
    :type multichannel: :class:`bool`
    """

    arguments = {
        "sigma": Number(min_value=0, default=1),
        "amount": Number(default=1),
        "multichannel": Type(bool, default=False),
    }

    def process(self, image, **kwargs):
        kwargs["radius"] = kwargs.pop("sigma")
        return unsharp_mask(image, preserve_range=True, **kwargs)
예제 #15
0
class Erode(Transform):
    """
    Erode is a transform that erodes away the boundaries of objects on an image.

    :param size: Kernel size, defaults to 5
    :type size: :class:`int`, optional
    :param iterations: Number of iterations, defaults to 1
    :type iterations: :class:`int`, optional
    """

    arguments = {
        "size": Number(min_value=1,
                       only_integer=True,
                       only_odd=True,
                       default=5),
        "iterations": Number(min_value=1, only_integer=True, default=1),
    }

    def process(self, image, **kwargs):
        kernel = np.ones((kwargs["size"], kwargs["size"]), np.uint8)
        return cv2.erode(image, kernel, iterations=kwargs["iterations"])
예제 #16
0
class Canny(Transform):
    """
    Canny is a transform that extracts the edges from the image using canny edge detection.

    :param low: Low threshold, defaults to 100
    :type low: :class:`int`, optional
    :param high: High threshold, defaults to 200
    :type high: :class:`int`, optional
    :param size: Aperture size, defaults to 5
    :type size: :class:`int`, optional
    :param sigma: Sigma for auto canny size, defaults to 0.33
    :type sigma: :class:`float`, optional
    """

    arguments = {
        "low":
        Number(min_value=1, max_value=255, only_integer=True, default="auto"),
        "high":
        Number(min_value=1, max_value=255, only_integer=True, default="auto"),
        "size":
        Number(min_value=3,
               max_value=7,
               only_integer=True,
               only_odd=True,
               default=3),
        "sigma":
        Number(min_value=0, default=0.33),
    }

    def process(self, image, **kwargs):
        if kwargs["low"] == "auto":
            v = np.median(image)
            kwargs["low"] = int(max(0, (1.0 - kwargs["sigma"]) * v))
        if kwargs["high"] == "auto":
            v = np.median(image)
            kwargs["high"] = int(min(255, (1.0 + kwargs["sigma"]) * v))
        return cv2.Canny(image,
                         kwargs["low"],
                         kwargs["high"],
                         apertureSize=kwargs["size"])
예제 #17
0
class Gradient(Transform):
    """
    Gradient is a transform that computes the gradient of an image. Available methods:

    \t**∙ sobel** - Gradient using Sobel kernel\n
    \t**∙ laplace** - Laplacian of an image\n
    \t**∙ morphological** - Morphological Gradient (difference between dilation and erosion).\n

    :param axis: Axis to compute, defaults to "both" (magnitude)
    :type axis: :class:`str`, optional
    :param size: Kernel size, defaults to 5
    :type size: :class:`int`, optional
    """

    methods = {
        "sobel": {
            "arguments": ["axis", "size"]
        },
        "morphological": {
            "arguments": ["size"]
        },
        "laplace": {},
    }
    default_method = "sobel"

    arguments = {
        "axis":
        Option(["both", "x", "y"], default=0),
        "size":
        Number(min_value=1,
               max_value=31,
               only_integer=True,
               only_odd=True,
               default=5),
    }

    def process(self, image, **kwargs):
        image = GrayScale().apply(image)
        if kwargs["method"] == "sobel":
            if kwargs["axis"] == "both":
                x = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=kwargs["size"])
                y = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=kwargs["size"])
                return (x**2 + y**2)**0.5
            if kwargs["axis"] == "x":
                return cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=kwargs["size"])
            else:
                return cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=kwargs["size"])
        elif kwargs["method"] == "laplace":
            return cv2.Laplacian(image, cv2.CV_64F)
        else:
            kernel = np.ones((kwargs["size"], kwargs["size"]), np.uint8)
            return cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel)
예제 #18
0
파일: color.py 프로젝트: easycv/easycv
class Brightness(Transform):
    """
    Brightness is a transform that changes the image brightness

    :param beta: Value of brightness to Add
    :type beta: :class:`int`
    """

    arguments = {
        "beta": Number(only_integer=True),
    }

    def process(self, image, **kwargs):
        image = cv2.addWeighted(image, 1, image, 0, kwargs["beta"])
        return image
예제 #19
0
파일: color.py 프로젝트: easycv/easycv
class Contrast(Transform):
    """
    Contrast is a transform that changes the image contrast

    :param alpha: Value of contrast to Add
    :type alpha: :class:`float`
    """

    arguments = {
        "alpha": Number(only_integer=False),
    }

    def process(self, image, **kwargs):
        image = cv2.addWeighted(image, kwargs["alpha"], image, 0, 0)
        return image
예제 #20
0
파일: color.py 프로젝트: easycv/easycv
class GammaCorrection(Transform):
    """
    GammaCorrection is a transform that corrects the contrast of images and displays.

    :param gamma: Gamma value
    :type gamma: :class:`Float`
    """

    arguments = {
        "gamma": Number(min_value=1e-30, default=1),
    }

    def process(self, image, **kwargs):
        table = np.array([((i / 255.0)**(1.0 / kwargs["gamma"])) * 255
                          for i in np.arange(0, 256)]).astype("uint8")
        return cv2.LUT(image, table)
예제 #21
0
파일: color.py 프로젝트: easycv/easycv
class Hue(Transform):
    """
    Hue is a transform that changes the image hue

    :param value: Value of Hue to Add
    :type value: :class:`int`
    """

    arguments = {
        "value": Number(only_integer=True),
    }

    def process(self, image, **kwargs):
        image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        image[:, :, 0] = (image[:, :, 0] + kwargs["value"]) % 180
        return cv2.cvtColor(image, cv2.COLOR_HSV2BGR)
예제 #22
0
파일: color.py 프로젝트: easycv/easycv
class FilterChannels(Transform):
    """
    FilterChannels is a transform that removes color channel(s).

    :param channels: List of channels to remove
    :type channels: :class:`list`
    :param scheme: Image color scheme (rgb or bgr), defaults to "rgb"
    :type scheme: :class:`str`, optional
    """

    arguments = {
        "channels": List(Number(min_value=0, max_value=2, only_integer=True)),
        "scheme": Option(["rgb", "bgr"], default=0),
    }

    def process(self, image, **kwargs):
        channels = np.array(kwargs["channels"])
        if kwargs["scheme"] == "rgb":
            channels = 2 - channels
        if len(channels) > 0:
            image[:, :, channels] = 0
        return image
예제 #23
0
class Perspective(Transform):
    """
        Perspective is a transform that changes the perspective of the image to the one given by \
        the provided corners/points. The new image will have the given points as corners.

        :param points: Corners of the desired perspective
        :type points: :class:`list`
    """

    arguments = {
        "points": List(List(Number(min_value=0, only_integer=True), length=2)),
    }

    def process(self, image, **kwargs):
        if len(kwargs["points"]) != 4:
            raise ValueError("Must receive 4 points.")

        corners = order_corners(kwargs["points"])
        tl, tr, br, bl = corners
        corners = np.array(corners, dtype="float32")

        new_width = max(distance(br, bl), distance(tr, tl))
        new_height = max(distance(tr, br), distance(tl, bl))

        dst = np.array(
            [
                [0, 0],
                [new_width - 1, 0],
                [new_width - 1, new_height - 1],
                [0, new_height - 1],
            ],
            dtype="float32",
        )

        shift_matrix = cv2.getPerspectiveTransform(corners, dst)
        warped = cv2.warpPerspective(image, shift_matrix,
                                     (new_width, new_height))

        return warped
예제 #24
0
class GradientAngle(Transform):
    """
    GradientAngle is a transform that computes the angles of the image gradient.

    :param size: Kernel size, defaults to 5
    :type size: :class:`int`, optional
    """

    arguments = {
        "size":
        Number(min_value=1,
               max_value=31,
               only_integer=True,
               only_odd=True,
               default=5)
    }

    def process(self, image, **kwargs):
        image = GrayScale().apply(image)
        x = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=kwargs["size"])
        y = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=kwargs["size"])
        return np.arctan2(x, y)
예제 #25
0
class Detect(Transform):
    methods = {
        "yolo": {
            "arguments": ["confidence", "threshold"]
        },
        "ssd": {
            "arguments": ["confidence"]
        },
    }
    default_method = "yolo"

    arguments = {
        "confidence": Number(min_value=0, max_value=1, default=0.5),
        "threshold": Number(min_value=0, max_value=1, default=0.3),
    }

    outputs = {
        "boxes":
        List(
            List(
                List(List(Number(min_value=0, only_integer=True), length=2),
                     length=2),
                List(Number(min_value=0, max_value=255, only_integer=True),
                     length=3),
                Type(str),
            ))
    }

    @staticmethod
    def labels(model):
        if model == "yolo":
            labels_path = get_resource("yolov3", "coco.names")
            labels = open(str(labels_path)).read().strip().split("\n")
        else:
            labels = [
                "background",
                "aeroplane",
                "bicycle",
                "bird",
                "boat",
                "bottle",
                "bus",
                "car",
                "cat",
                "chair",
                "cow",
                "diningtable",
                "dog",
                "horse",
                "motorbike",
                "person",
                "pottedplant",
                "sheep",
                "sofa",
                "train",
                "tvmonitor",
            ]
        return labels

    def process(self, image, **kwargs):
        labels = self.labels(kwargs["method"])
        colors = np.random.randint(0,
                                   255,
                                   size=(len(labels), 3),
                                   dtype="uint8")

        if kwargs["method"] == "yolo":
            config = get_resource("yolov3", "yolov3.cfg")
            weights = get_resource("yolov3", "yolov3.weights")

            net = cv2.dnn.readNetFromDarknet(str(config), str(weights))

            layers = net.getLayerNames()
            layers = [layers[i[0] - 1] for i in net.getUnconnectedOutLayers()]

            blob = cv2.dnn.blobFromImage(image,
                                         1 / 255.0, (416, 416),
                                         swapRB=True,
                                         crop=False)

            h, w = image.shape[:2]
            net.setInput(blob)
            outputs = net.forward(layers)

            rectangles = []
            confidences = []
            class_ids = []

            for output in outputs:
                for detection in output:
                    scores = detection[5:]
                    class_id = np.argmax(scores)
                    confidence = scores[class_id]

                    if confidence > kwargs["confidence"]:
                        # scale the bounding box back
                        rectangle = detection[0:4] * np.array([w, h, w, h])
                        (centerX, centerY, width,
                         height) = rectangle.astype("int")

                        # compute top-left corner
                        x = int(centerX - (width / 2))
                        y = int(centerY - (height / 2))

                        rectangles.append([x, y, int(width), int(height)])
                        confidences.append(float(confidence))
                        class_ids.append(class_id)

            # apply non-maximum suppression
            indexes_to_keep = cv2.dnn.NMSBoxes(rectangles, confidences,
                                               kwargs["confidence"],
                                               kwargs["threshold"])

            boxes = []
            if len(indexes_to_keep) > 0:
                for i in indexes_to_keep.flatten():
                    (x, y) = (rectangles[i][0], rectangles[i][1])
                    (w, h) = (rectangles[i][2], rectangles[i][3])
                    color = [int(c) for c in colors[class_ids[i]]]
                    label = "{}: {:.4f}".format(labels[int(class_ids[i])],
                                                confidences[i])
                    boxes.append([[(x, y), (w, h)], color, label])

            return {"boxes": boxes}
        else:
            prototxt = get_resource("ssd-mobilenet",
                                    "MobileNetSSD_deploy.prototxt")
            model = get_resource("ssd-mobilenet",
                                 "MobileNetSSD_deploy.caffemodel")
            net = cv2.dnn.readNetFromCaffe(str(prototxt), str(model))

            (h, w) = image.shape[:2]
            blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)),
                                         0.007843, (300, 300), 127.5)
            net.setInput(blob)
            detections = net.forward()

            boxes = []
            for i in np.arange(0, detections.shape[2]):
                confidence = detections[0, 0, i, 2]

                if confidence > kwargs["confidence"]:
                    idx = int(detections[0, 0, i, 1])
                    box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
                    (startX, startY, endX, endY) = box.astype("int")
                    width, height = int(endX - startX), int(endY - startY)
                    label = "{}: {:.4f}".format(labels[idx], confidence)
                    color = [int(c) for c in colors[idx]]

                    boxes.append([[(startX, startY), (width, height)], color,
                                  label])

            return {"boxes": boxes}
예제 #26
0
class Lines(Transform):
    """
    Lines is a transform that detects lines in an image using the Hough Transform.

    :param low: Low canny threshold, defaults to 50
    :type low: :class:`int`, optional
    :param high: High canny threshold, defaults to 150
    :type high: :class:`int`, optional
    :param size: Gaussian kernel size (canny), defaults to 3
    :type size: :class:`int`, optional
    :param rho: Distance resolution of the accumulator in pixels, defaults to 1
    :type rho: :class:`int`/:class:`float`, optional
    :param theta: Angle resolution of the accumulator in radians, defaults to pi/180
    :type theta: :class:`int`/:class:`float`, optional
    :param threshold: Threshold of votes, defaults to 200
    :type threshold: :class:`int`, optional
    :param min_size: Minimum line length, defaults to 3
    :type min_size: :class:`int`/:class:`float`, optional
    :param max_gap: Maximum gap between lines to treat them as single line, defaults to 3
    :type max_gap: :class:`int`/:class:`float`, optional
    """

    methods = {
        "normal": {
            "arguments": ["low", "high", "size", "rho", "theta", "threshold"]
        },
        "probablistic": {
            "arguments": [
                "low",
                "high",
                "size",
                "rho",
                "theta",
                "threshold",
                "min_size",
                "max_gap",
            ]
        },
    }

    default_method = "normal"

    arguments = {
        "low":
        Number(min_value=1, max_value=255, only_integer=True, default=50),
        "high":
        Number(min_value=1, max_value=255, only_integer=True, default=150),
        "size":
        Number(min_value=3,
               max_value=7,
               only_integer=True,
               only_odd=True,
               default=3),
        "rho":
        Number(min_value=0, default=1),
        "theta":
        Number(min_value=0, max_value=np.pi / 2, default=np.pi / 180),
        "threshold":
        Number(min_value=0, only_integer=True, default=200),
        "min_size":
        Number(min_value=0, default=3),
        "max_gap":
        Number(min_value=0, default=3),
    }

    outputs = {
        "lines":
        List(
            List(List(Number(min_value=0, only_integer=True), length=2),
                 length=2))
    }

    def process(self, image, **kwargs):
        gray = GrayScale().apply(image)
        edges = Canny(low=kwargs["low"],
                      high=kwargs["high"],
                      size=kwargs["size"]).apply(gray)
        if kwargs["method"] == "normal":
            h_lines = cv2.HoughLines(edges, kwargs["rho"], kwargs["theta"],
                                     kwargs["threshold"])
            lines = []
            if h_lines is not None:
                for rho, theta in h_lines[:, 0]:
                    x1 = int(rho * np.cos(theta))
                    y1 = int(rho * np.sin(theta))
                    if x1 > y1:
                        y1 = 0
                    elif y1 > x1:
                        x1 = 0
                    x2 = (rho - image.shape[0] * np.sin(theta)) / np.cos(theta)
                    y2 = (rho - image.shape[1] * np.cos(theta)) / np.sin(theta)
                    if 0 <= x2 <= image.shape[1] or np.sin(theta) == 0:
                        y2 = image.shape[0]
                    elif 0 <= y2 <= image.shape[0] or np.cos(theta) == 0:
                        x2 = image.shape[1]
                    lines.append([[int(x1), int(y1)], [int(x2), int(y2)]])
        else:
            lines = cv2.HoughLines(
                edges,
                kwargs["rho"],
                kwargs["theta"],
                kwargs["threshold"],
                kwargs["min_size"],
                kwargs["max_gap"],
            )
        return {"lines": lines}
예제 #27
0
class Select(Transform):
    """
    Select is a transform that allows the user to select a shape or a mask in an image. Currently \
    supported shapes:

    \t**∙ rectangle** - Rectangle Shape\n
    \t**∙ point** - Point\n
    \t**∙ ellipse** - Ellipse Shape\n
    \t**∙ mask** - Mask\n

    :param n: Number of points to select
    :type n: :class:`int`
    :param brush: Brush size
    :type brush: :class:`int`
    :param color: Brush color
    :type color: :class:`List`
    """

    methods = {
        "rectangle": {"arguments": [], "outputs": ["rectangle"]},
        "point": {"arguments": ["n"], "outputs": ["points"]},
        "ellipse": {"arguments": [], "outputs": ["ellipse"]},
        "mask": {"arguments": ["brush", "color"], "outputs": ["mask"]},
    }
    default_method = "rectangle"

    arguments = {
        "n": Number(only_integer=True, min_value=0, default=2),
        "brush": Number(only_integer=True, min_value=0, default=20),
        "color": List(
            Number(only_integer=True, min_value=0, max_value=255),
            length=3,
            default=(0, 255, 0),
        ),
    }

    outputs = {
        # rectangle
        "rectangle": List(
            List(Number(min_value=0, only_integer=True), length=2), length=2
        ),
        # ellipse
        "ellipse": List(
            List(Number(only_integer=True, min_value=0), length=2),
            Number(min_value=0, only_integer=True),
            Number(min_value=0, only_integer=True),
        ),
        # point
        "points": List(List(Number(min_value=0, only_integer=True), length=2)),
        # mask
        "mask": Image(),
    }

    def process(self, image, **kwargs):
        if "DISPLAY" not in os.environ:
            raise Exception("Can't run selectors without a display!")

        if kwargs["method"] == "mask":
            mask = np.zeros(image.shape, np.uint8)

            global drawing
            drawing = False

            def paint_draw(event, x, y, flags, param):
                global ix, iy, drawing

                if event == cv2.EVENT_LBUTTONDOWN:
                    drawing = True
                elif event == cv2.EVENT_LBUTTONUP:
                    drawing = False
                elif event == cv2.EVENT_MOUSEMOVE and drawing:
                    cv2.line(mask, (ix, iy), (x, y), kwargs["color"], kwargs["brush"])

                ix, iy = x, y

                return x, y

            cv2.namedWindow("Select Mask", cv2.WINDOW_KEEPRATIO)
            cv2.resizeWindow("Select Mask", image.shape[0], image.shape[1])
            cv2.setMouseCallback("Select Mask", paint_draw)

            while cv2.getWindowProperty("Select Mask", cv2.WND_PROP_VISIBLE) >= 1:
                cv2.imshow("Select Mask", cv2.addWeighted(image, 0.8, mask, 0.2, 0))
                key_code = cv2.waitKey(1)

                if (key_code & 0xFF) == ord("q"):
                    cv2.destroyAllWindows()
                    break
                elif (key_code & 0xFF) == ord("+"):
                    kwargs["brush"] += 1
                elif (key_code & 0xFF) == ord("-") and kwargs["brush"] > 1:
                    kwargs["brush"] -= 1

            mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
            mask[mask != 0] = 255

            return {"mask": easycv.image.Image(mask)}

        mpl.use("Qt5Agg")

        fig, current_ax = plt.subplots()
        plt.tick_params(
            axis="both",
            which="both",
            bottom=False,
            top=False,
            left=False,
            right=False,
            labelbottom=False,
            labelleft=False,
        )

        def empty_callback(e1, e2):
            pass

        def selector(event):
            if event.key in ["Q", "q"]:
                plt.close(fig)

        res = []
        current_ax.imshow(prepare_image_to_output(image))
        plt.gcf().canvas.set_window_title("Selector")

        if kwargs["method"] == "rectangle":
            selector.S = RectangleSelector(
                current_ax,
                empty_callback,
                useblit=True,
                button=[1, 3],
                minspanx=5,
                minspany=5,
                spancoords="pixels",
                interactive=True,
            )
        elif kwargs["method"] == "ellipse":
            selector.S = EllipseSelector(
                current_ax,
                empty_callback,
                drawtype="box",
                interactive=True,
                useblit=True,
            )
        else:

            def onclick(event):
                if event.xdata is not None and event.ydata is not None:
                    res.append((int(event.xdata), int(event.ydata)))
                    plt.plot(
                        event.xdata, event.ydata, marker="o", color="cyan", markersize=4
                    )
                    fig.canvas.draw()
                    if len(res) == kwargs["n"]:
                        plt.close(fig)

            plt.connect("button_press_event", onclick)

        plt.connect("key_press_event", selector)
        plt.show(block=True)

        if kwargs["method"] == "rectangle":
            x, y = selector.S.to_draw.get_xy()
            x = int(round(x))
            y = int(round(y))
            width = int(round(selector.S.to_draw.get_width()))
            height = int(round(selector.S.to_draw.get_height()))

            if width == 0 or height == 0:
                raise InvalidSelectionError("Must select a rectangle.")

            return {"rectangle": [(x, y), (x + width, y + height)]}

        elif kwargs["method"] == "ellipse":
            width = int(round(selector.S.to_draw.width))
            height = int(round(selector.S.to_draw.height))
            center = [int(round(x)) for x in selector.S.to_draw.get_center()]
            if width == 0 or height == 0:
                raise InvalidSelectionError("Must select an ellipse.")
            return {"ellipse": [tuple(center), int(width / 2), int(height / 2)]}
        else:
            if len(res) != kwargs["n"]:
                raise InvalidSelectionError(
                    "Must select {} points.".format(kwargs["n"])
                )
            return {"points": res}
예제 #28
0
파일: noise.py 프로젝트: easycv/easycv
class Noise(Transform):
    """
        Noise is a transform that adds various types of noise to the image. Currently supported\
        types are:

        \t**∙ gaussian** - Gaussian-distributed additive noise\n
        \t**∙ poisson** - Poisson-distributed noise generated from the data\n
        \t**∙ salt** - Replaces random pixels with 255\n
        \t**∙ pepper** - Replaces random pixels with 0\n
        \t**∙ sp** - Replaces random pixels with either 1 or 0.\n
        \t**∙ speckle** - Multiplicative noise using ``out = image + n * image``, where \
        n is uniform noise with specified mean & variance.\n

        :param method: Type of noise to add
        :type method: :class:`str`, optional
        :param seed: Seed for the random generator, by default generates random seed
        :type seed: :class:`int`, optional
        :param clip: If True the output will be clipped to [0, 255], defaults to True
        :type clip: :class:`bool`, optional
        :param mean: Mean of random distribution. Used in ‘gaussian’ and ‘speckle’, defaults to 0
        :type mean: :class:`float`, optional
        :param var: Variance of random distribution. Used in ‘gaussian’ and ‘speckle’, \
        defaults to 0.01
        :type var: :class:`float`, optional
        :param amount: Percentage of image pixels to replace with noise.Used in ‘salt’, \
        ‘pepper’, and ‘s&p’. Default : 0.05
        :type amount: :class:`float`, optional
        :param salt_vs_pepper: Proportion of salt vs. pepper noise for ‘s&p’ on range [0, 1].\
         Higher values represent more salt. Default : 0.5 (equal amounts)
        :type salt_vs_pepper: :class:`float`, optional
    """

    methods = {
        "gaussian": {
            "arguments": ["mean", "var", "seed", "clip"]
        },
        "salt": {
            "arguments": ["amount", "seed", "clip"]
        },
        "pepper": {
            "arguments": ["amount", "seed", "clip"]
        },
        "sp": {
            "arguments": ["amount", "salt_vs_pepper", "seed", "clip"]
        },
        "poisson": {
            "arguments": ["seed", "clip"]
        },
    }
    method_name = "mode"
    default_method = "gaussian"

    arguments = {
        "seed": Number(min_value=0, max_value=2**32 - 1, default=False),
        "clip": Type(bool, default=True),
        "mean": Number(default=0),
        "var": Number(min_value=0, max_value=255, default=2.5),
        "amount": Number(min_value=0, max_value=1, default=0.05),
        "salt_vs_pepper": Number(min_value=0, max_value=1, default=0.5),
    }

    def process(self, image, **kwargs):
        kwargs["seed"] = kwargs["seed"] if kwargs["seed"] else None
        if kwargs["mode"] == "gaussian":
            kwargs["var"] = kwargs["var"] / 255
        if kwargs["mode"] == "sp":
            kwargs["mode"] = "s&p"
        return random_noise(image, **kwargs)
예제 #29
0
class Blur(Transform):
    """
    Blur is a transform that blurs an image.

    \t**∙ uniform** - Uniform Filter\n
    \t**∙ gaussian** - Gaussian-distributed additive noise\n
    \t**∙ median** - Median Filter\n
    \t**∙ bilateral** - Edge preserving blur\n

    :param method: Blur method to be used, defaults to "uniform"
    :type method: :class:`str`, optional
    :param size: Kernel size, defaults to auto
    :type size: :class:`int`, optional
    :param sigma: Sigma value, defaults to 0
    :type sigma: :class:`int`, optional
    :param sigma_color: Sigma for color space, defaults to 75
    :type sigma_color: :class:`int`, optional
    :param sigma_space: Sigma for coordinate space, defaults to 75
    :type sigma_space: :class:`int`, optional
    :param truncate: Truncate the filter at this many standard deviations., defaults to 4
    :type truncate: :class:`int`, optional
    """

    methods = {
        "uniform": {
            "arguments": ["size"]
        },
        "gaussian": {
            "arguments": ["size", "sigma", "truncate"]
        },
        "median": {
            "arguments": ["size"]
        },
        "bilateral": {
            "arguments": ["size", "sigma_color", "sigma_space"]
        },
    }
    default_method = "gaussian"

    arguments = {
        "size":
        Number(min_value=1, only_integer=True, only_odd=True, default="auto"),
        "sigma":
        Number(min_value=0, default=1),
        "sigma_color":
        Number(min_value=0, default=75),
        "sigma_space":
        Number(min_value=0, default=75),
        "truncate":
        Number(min_value=0, default=4),
    }

    def process(self, image, **kwargs):
        if kwargs["method"] == "uniform":
            return cv2.blur(image, (kwargs["size"], kwargs["size"]))
        elif kwargs["method"] == "gaussian":
            if kwargs["size"] == "auto":
                kwargs["size"] = 2 * int(kwargs["sigma"] * kwargs["truncate"] +
                                         0.5) + 1
            return cv2.GaussianBlur(image, (kwargs["size"], kwargs["size"]),
                                    kwargs["sigma"])
        elif kwargs["method"] == "median":
            return cv2.medianBlur(image, kwargs["size"])
        else:
            if kwargs["size"] == "auto":
                kwargs["size"] = 5
            return cv2.bilateralFilter(image, kwargs["size"],
                                       kwargs["sigma_color"],
                                       kwargs["sigma_space"])
예제 #30
0
파일: draw.py 프로젝트: easycv/easycv
class Draw(Transform):
    """
    The Draw transform provides a way to draw 2D shapes or text on a image. Currently supported\
    methods are:

    \t**∙ ellipse** - Draw an ellipse on an image\n
    \t**∙ line** - Draw a line on an image\n
    \t**∙ polygon** - Draw a polygon on an image\n
    \t**∙ rectangle** - Draw a rectangle on an image\n
    \t**∙ text** - Write text on an image\n

    :param ellipse: Tuple made up by: three arguments(center(int,int), axis1(int), axis2(int)) \
    regarding the ellipse.
    :type ellipse: :class:`tuple`
    :param color: color of the shape in BGR, defaults to "(0,0,0)" - black.
    :type color: :class:`list`/:class:`tuple`, optional
    :param end_angle: Ending angle of the elliptic arc in degrees, defaults to "360".
    :type end_angle: :class:`int`, optional
    :param font: Font to be used, defaults to "SIMPLEX".
    :type font: :class:`str`, optional
    :param filled: True if shape is to be filled, defaults to "false".
    :type filled: :class:`bool`, optional
    :param closed: If true, a line is drawn from the last vertex to the first
    :type closed: :class:`str`, optional
    :param line_type: Line type to be used when drawing a shape, defaults to "line_AA".
    :type line_type: :class:`str`, optional
    :param org: Bottom-left corner of the text in the image.
    :type org: :class:`tuple`
    :param pt1: First point to define a shape.
    :type pt1: :class:`tuple`
    :param pt2: Second point to define a shape.
    :type pt2: :class:`tuple`
    :param points: A list of point.
    :type points: :class:`list`/:class:`tuple`
    :param radius: Radius of the circle.
    :type radius: :class:`int`
    :param rectangle: Tuple made up by: two points(upper left corner(int,int), lower right \
    corner(int,int))regarding the rectangle.
    :type rectangle: :class:`tuple`
    :param rotation_angle: Angle of rotation.
    :type rotation_angle: :class:`int`
    :param size: Size of the text to be drawn, defaults to "5".
    :type size: :class:`int`, optional
    :param start_angle: Starting angle of the elliptic arc in degrees, defaults to "0".
    :type start_angle: :class:`int`, optional
    :param text: Text string to be drawn.
    :type text: :class:`str`
    :param thickness: Thickness of the line used, defaults to "5".
    :type thickness: :class:`int`, optional
    :param x_mirror: When true the text is mirrored in the x axis, defaults to "false".
    :type x_mirror: :class:`bool`
    """

    methods = {
        "ellipse": {
            "arguments": [
                "ellipse",
                "rotation_angle",
                "start_angle",
                "end_angle",
                "filled",
                "color",
                "thickness",
                "line_type",
            ]
        },
        "line": {
            "arguments": ["pt1", "pt2", "color", "thickness", "line_type"]
        },
        "polygon": {
            "arguments": [
                "points",
                "closed",
                "color",
                "thickness",
                "line_type",
                "filled",
            ]
        },
        "rectangle": {
            "arguments":
            ["rectangles", "color", "thickness", "line_type", "filled"]
        },
        "text": {
            "arguments": [
                "text",
                "org",
                "font",
                "size",
                "x_mirror",
                "color",
                "thickness",
                "line_type",
            ]
        },
        "boxes": {
            "arguments": ["boxes", "line_type", "font", "size"]
        },
        "rectangles": {
            "arguments":
            ["rectangles", "color", "thickness", "line_type", "filled"]
        },
    }

    arguments = {
        "ellipse":
        List(
            List(Number(only_integer=True, min_value=0), length=2),
            Number(min_value=0, only_integer=True),
            Number(min_value=0, only_integer=True),
        ),
        "rectangle":
        List(List(Number(min_value=0, only_integer=True), length=2), length=2),
        "rectangles":
        List(
            List(List(Number(min_value=0, only_integer=True), length=2),
                 length=2)),
        "color":
        List(Number(min_value=0, max_value=255), length=3, default=(0, 0, 0)),
        "end_angle":
        Number(default=360),
        "font":
        Option(
            [
                "SIMPLEX",
                "PLAIN",
                "DUPLEX",
                "COMPLEX",
                "TRIPLEX",
                "COMPLEX_SMALL",
                "SCRIPT_SIMPLEX",
                "SCRIPT_COMPLEX",
            ],
            default=0,
        ),
        "filled":
        Type(bool, default=False),
        "closed":
        Type(bool, default=True),
        "line_type":
        Option(["line_AA", "line_4", "line_8"], default=0),
        "org":
        List(Number(min_value=0, only_integer=True), length=2),
        "pt1":
        List(Number(min_value=0, only_integer=True), length=2),
        "pt2":
        List(Number(min_value=0, only_integer=True), length=2),
        "points":
        List(List(Number(min_value=0, only_integer=True), length=2)),
        "radius":
        Number(min_value=0),
        "rotation_angle":
        Number(default=0),
        "size":
        Number(min_value=0, default=5),
        "start_angle":
        Number(default=0),
        "text":
        Type(str),
        "thickness":
        Number(min_value=0, default=5, only_integer=True),
        "x_mirror":
        Type(bool, default=False),
        "boxes":
        List(
            List(
                List(List(Number(min_value=0, only_integer=True), length=2),
                     length=2),
                List(Number(min_value=0, max_value=255, only_integer=True),
                     length=3),
                Type(str),
            )),
    }

    def process(self, image, **kwargs):
        if len(image.shape) < 3:
            kwargs["color"] = ((0.3 * kwargs["color"][0]) +
                               (0.59 * kwargs["color"][1]) +
                               (0.11 * kwargs["color"][2]))

        method = kwargs.pop("method")

        kwargs["lineType"] = lines[kwargs.pop("line_type")]

        if method == "line":
            kwargs["pt1"] = tuple(kwargs["pt1"])
            kwargs["pt2"] = tuple(kwargs["pt2"])
            return cv.line(image, **kwargs)

        if method == "polygon":
            kwargs["pts"] = kwargs.pop("points")
            kwargs["pts"] = np.array(kwargs.pop("pts"), np.int32)
            kwargs["pts"] = [kwargs["pts"].reshape((-1, 1, 2))]
            if kwargs.pop("filled"):
                kwargs.pop("thickness")
                kwargs.pop("closed")
                return cv.fillPoly(image, **kwargs)
            else:
                kwargs["isClosed"] = kwargs.pop("closed")
                return cv.polylines(image, **kwargs)

        if method == "text":
            kwargs["org"] = tuple(kwargs["org"])
            kwargs["font"] = font[kwargs["font"]]
            kwargs["fontFace"] = kwargs.pop("font")
            kwargs["fontScale"] = kwargs.pop("size")
            kwargs["bottomLeftOrigin"] = kwargs.pop("x_mirror")
            return cv.putText(image, **kwargs)

        # to make a circle/ellipse/line/rectangle filled thickness must be negative/cv.FILLED
        if kwargs.pop("filled", False):
            kwargs["thickness"] = cv.FILLED

        if method == "ellipse":
            return cv.ellipse(
                image,
                kwargs["ellipse"][0],
                (kwargs["ellipse"][1], kwargs["ellipse"][2]),
                kwargs["rotation_angle"],
                kwargs["start_angle"],
                kwargs["end_angle"],
                kwargs["color"],
                kwargs["thickness"],
                kwargs["lineType"],
            )

        if method == "rectangle":
            pts = kwargs.pop("rectangle")
            return cv.rectangle(image, pts[0], pts[1], **kwargs)

        if method == "boxes":
            kwargs["font"] = font[kwargs["font"]]
            kwargs["fontFace"] = kwargs.pop("font")
            size = kwargs.pop("size")
            for box in kwargs.pop("boxes"):
                image = cv.rectangle(
                    image,
                    box[0][0],
                    (box[0][0][0] + box[0][1][0], box[0][0][1] + box[0][1][1]),
                    tuple(box[1]),
                    thickness=2,
                )
                kwargs["org"] = (box[0][0][0], box[0][0][1] - 5)
                kwargs["color"] = tuple(box[1])
                kwargs["fontScale"] = size * (box[0][1][0]) * 0.0008
                kwargs["thickness"] = int(2 / (box[0][1][0] * 0.5))
                image = cv.putText(image, box[2], **kwargs)
            return image

        if method == "rectangles":
            for rectangle in kwargs.pop("rectangles"):
                image = cv.rectangle(image, rectangle[0], rectangle[1],
                                     **kwargs)
            return image