Beispiel #1
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
        }
Beispiel #2
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}
Beispiel #3
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}
Beispiel #4
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}
Beispiel #5
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]}
Beispiel #6
0
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]
            }
Beispiel #7
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
Beispiel #8
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)
Beispiel #9
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
Beispiel #10
0
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
Beispiel #11
0
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
Beispiel #12
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}
Beispiel #13
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}
Beispiel #14
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}