Exemplo n.º 1
0
class Input_process():
    def __init__(self, create_windows=True, difficulty=1):
        # create windows for output
        if create_windows:
            self.input_window = self.create_window("Input")
            self.shape_window = self.create_window("Shape")
            self.output_window = self.create_window("Processed Output")

        self.img_ops = Image_operations()
        self.mode = "ShapeMatch"
        self.color_calibration = None
        self.difficulty = difficulty

        self.shape_collection = Shape_Collection(difficulty)
        self.init_camera()

        try:
            if difficulty == 2:
                filename = SHAPES_FILENAME_PARTS[0] + str(5) + SHAPES_FILENAME_PARTS[1]
            else:
                filename = SHAPES_FILENAME_PARTS[0] + str(4) + SHAPES_FILENAME_PARTS[1]
            with open(filename):
                self.shape_collection.deserialize()
        except IOError:
            print "No valid pkl found. Generating shapes..."
            self.shape_collection.serialize()
            self.shape_collection.deserialize()

    def init_camera(self):
        self.cam = cv2.VideoCapture(CAMERA_PORT)
        self.cam.set(3, CAM_RES_WIDTH)
        self.cam.set(4, CAM_RES_HEIGHT)
        self.debug_img = None

        stream = None
        cnt = 0
        while not stream and cnt < 5:
            print "Waiting for camera..."
            sleep(0.5)
            cnt += 1
            stream, img = self.read_cam()

    def read_cam(self):
        if DEBUG:
            if self.debug_img is None:
                print os.path.join("Resources", "Images", "debug_input.png")
                self.debug_img = cv2.imread(os.path.join("Resources", "Images", "debug_input.png"))
            return True, self.debug_img.copy()
        else:
            return self.cam.read()

    def run(self):
        pass

    def angle_between(self, a, b):
        # based on http://stackoverflow.com/questions/2827393/angles-between-two-n-dimensional-vectors-in-python
        """ Returns the angle in radians between vectors 'v1' and 'v2'::
        """
        v0 = np.array([a.slope_v.x, a.slope_v.y])
        v1 = np.array([b.slope_v.x, b.slope_v.y])
        v0_u = unit_vector(v0)
        v1_u = unit_vector(v1)
        angle = np.arccos(np.dot(v0_u, v1_u))
        if np.isnan(angle):
            if (v0_u == v1_u).all():
                return 0.0
            else:
                return 360.0
        return np.degrees(angle)

    def calc_angles(self, lines):
        res = []
        for i in range(0, len(lines)):
            res.append(self.angle_between(lines[i], lines[(i + 1) % len(lines)]))
        return res

    def compare_both(self, angles0, angles1, length0, length1):
        best_angles_diff = None
        best_length_diff = None
        best_start = None
        count = len(angles0)

        for start in range(0, count):
            diff_angles_sum = 0
            diff_length_sum = 0
            found_too_big_angle = False
            found_too_big_length = False
            for i in range(0, count):
                # ANGLES
                diff_angles = abs(abs(angles0[i]) - abs(angles1[(i + start) % count]))
                if diff_angles < MAX_ANGLE_DIFF_FOR_MATCH:
                    diff_angles_sum += diff_angles
                else:
                    found_too_big_angle = True
                    break

                # LENGTH
                diff_length = abs(length0[i] - length1[(i + start) % count])
                if diff_length < MAX_EDGE_DIFF_FOR_MATCH:
                    diff_length_sum += diff_length
                else:
                    found_too_big_length = True
                    break

            if not found_too_big_angle \
                and not found_too_big_length \
                and (best_angles_diff is None or diff_angles_sum < best_angles_diff) \
                and (best_length_diff is None or diff_length_sum < best_length_diff):
                best_angles_diff = diff_angles_sum
                best_length_diff = diff_length_sum
                best_start = start

        if best_angles_diff is not None and best_angles_diff < MAX_ANGLE_DIFF_SUM_FOR_MATCH \
            and best_length_diff is not None and best_length_diff < MAX_EDGE_DIFF_SUM_FOR_MATCH:
            return True, best_start
        else:
            return False, None

    def compare_angles(self, a, b):
        best_diff = None
        best_start = None

        for start in range(0, len(a)):
            diff_sum = 0
            found_too_big_angle = False
            for i in range(0, len(a)):
                diff = abs(abs(b[i]) - abs(a[(i + start) % len(a)]))
                if diff < 20:
                    diff_sum += diff
                else:
                    found_too_big_angle = True
                    break
            if not found_too_big_angle and (best_diff is None or diff_sum < best_diff):
                best_diff = diff_sum
                best_start = start

        if best_diff is not None and best_diff < 50:
            return True, best_start
        else:
            return False, None

    def compare_length(self, shape, combined_lines):
        orig_length = shape.lengths

        orig_sum = sum(orig_length)
        for i, l in enumerate(orig_length):
            orig_length[i] = l / orig_sum

        input_length = []
        for line in combined_lines:
            length = abs(line.length())
            input_length.append(length)

        input_sum = sum(input_length)
        for i, l in enumerate(input_length):
            input_length[i] = l / input_sum

        best_diff = None
        for start in range(0, len(orig_length)):
            diff_sum = 0
            found_too_big_length = False
            for i in range(0, len(input_length)):
                diff = abs(input_length[i] - orig_length[(i + start) % len(orig_length)])
                if diff < 0.1:
                    diff_sum += diff
                else:
                    found_too_big_length = True
                    break
            if not found_too_big_length and (best_diff is None or diff_sum < best_diff):
                best_diff = diff_sum

        return best_diff is not None and best_diff < MAX_EDGE_DIFF_SUM_FOR_MATCH

    def process_shape_matching(self, img):
        succ_status = DETECTION_NONE
        out_img, polys = self.img_ops.exec_shape_match(img, show=True)
        combined_lines_img = img.copy()
        indices = set()

        for contour in polys:
            succ_status = max(DETECTION_SHAPE, succ_status)
            color = RED_BGR
            lines = []
            for i, point in enumerate(contour):
                lines.append(Line(Vector2D(x=point[0], y=point[1]),
                                  Vector2D(x=contour[(i + 1) % len(contour)][0], y=contour[(i + 1) % len(contour)][1])))

            combined_lines = self.combine_line(lines)

            # found the right count of edges.
            if len(combined_lines) == TOWER_EDGE_COUNT:
                index, start, succ_status = self.match_contour(combined_lines)
                if index is not None:
                    if MATCH_COLOR_TRIANGLE:
                        if self.difficulty == 0:
                            match_color, succ_status = self.match_color_triangle_simple(combined_lines_img, combined_lines,
                                                                             self.shape_collection.shapes[index], start)
                        else:
                            match_color, succ_status = self.match_color_triangle(combined_lines_img, combined_lines,
                                                                             self.shape_collection.shapes[index], start, self.difficulty)
                        if match_color:
                            color = GREEN_BGR
                            indices.add(index)
                        else:
                            color = RED_BGR
                    else:
                        color = GREEN_BGR
                        indices.add(index)

            for i, line in enumerate(combined_lines):
                line.draw(combined_lines_img, color)

        return combined_lines_img, indices, succ_status

    def process_img(self, img):
        out_img = None

        if self.mode == "Canny":
            out_img = self.img_ops.canny_filter(img)
        elif self.mode == "GoodFeaturesToTrack":
            out_img = self.img_ops.exec_goodFeaturesToTrack_filter(img)
        elif self.mode == "HoughCircles":
            out_img = self.img_ops.exec_houghCircles_filter(img)
        elif self.mode == "HoughLines":
            out_img, lines = self.img_ops.exec_houghLines_filter(img, show=True)
        elif self.mode == "HoughLinesP":
            out_img, lines = self.img_ops.exec_houghLinesP_filter(img, show=True)
        elif self.mode == "FindContours":
            out_img = self.img_ops.exec_find_contours(img)
        elif self.mode == "FindContoursMod":
            out_img = self.img_ops.exec_find_contours_mod(img)
        elif self.mode == "ContourArea":
            out_img = self.img_ops.exec_contour_area(img)
        elif self.mode == "ShapeMatch":
            out_img, polys = self.img_ops.exec_shape_match(img, self.shape_collection, show=True)

            cv2.imshow(self.shape_window, self.shape_collection.get_combined_img("original"))

            combined_lines_img = out_img.copy()

            for contour in polys:
                color = RED_BGR
                lines = []
                for i, point in enumerate(contour):
                    lines.append(Line(Vector2D(x=point[0], y=point[1]), Vector2D(x=contour[(i + 1) % len(contour)][0],
                                                                                 y=contour[(i + 1) % len(contour)][1])))

                combined_lines = self.combine_line(lines)

                # found the right count of edges.
                if len(combined_lines) == TOWER_EDGE_COUNT:
                    index, start, succ_status = self.match_contour(combined_lines)
                    if index is not None:
                        print "found match at", index
                        if MATCH_COLOR_TRIANGLE:
                            if self.match_color_triangle(img, combined_lines, self.shape_collection.shapes[index],
                                                         start):
                                print "found color triangle match"
                                color = GREEN_BGR
                            else:
                                print "found contour but no color match"
                                color = RED_BGR
                        else:
                            color = GREEN_BGR

                for i, line in enumerate(combined_lines):
                    line.draw(combined_lines_img, color)

            out_comb = self.combine_image_output([out_img, combined_lines_img])
            cv2.imshow(self.output_window, out_comb)
        if self.mode != "ShapeMatch":
            cv2.imshow(self.output_window, out_img)
        cv2.imshow(self.input_window, img)


    def save_last_images(self, images):
        # save last images as file

        directory = "../tmp/images/"
        if not os.path.exists(directory):
            os.makedirs(directory)

        for i, image in enumerate(images):
            filename = directory + str(i) + ".png"
            print "saving ", filename
            cv2.imwrite(filename, image)

    def create_window(self, name):
        cv2.namedWindow(name, cv2.CV_WINDOW_AUTOSIZE)
        return name

    def combine_image_output(self, imgs):
        # first img in collection defines height.
        h = imgs[0].shape[0]
        w = 0
        for img in imgs:
            w += img.shape[1]

        all_shapes = np.zeros((h, w, 3), np.uint8)
        current_w = 0
        for img in imgs:
            all_shapes[:h, current_w:current_w + img.shape[1], :] = img
            current_w += img.shape[1]
        return all_shapes

    def process_key_input(self):
        key = cv2.waitKey(10)
        if key != -1:
            if key == ord('1'):
                self.mode = "Canny"
            elif key == ord('2'):
                self.mode = "GoodFeaturesToTrack"
            elif key == ord('3'):
                self.mode = "HoughCircles"
            elif key == ord('4'):
                self.mode = "HoughLines"
            elif key == ord('5'):
                self.mode = "HoughLinesP"
            elif key == ord('6'):
                self.mode = "FindContours"
            elif key == ord('7'):
                self.mode = "FindContoursMod"
            elif key == ord('8'):
                self.mode = "ShapeMatch"
            elif key == ord('9'):
                self.mode = "ShapeMatchExtended"
            elif key == ESC_KEY:
                return True

            print "Using now: " + self.mode
        return False


    def combine_line(self, lines):
        finished = False

        # remove too short lines
        for i, line in enumerate(lines):
            if line.length() < 5:
                lines[(i + 1) % len(lines)].start = line.start
                lines.remove(line)

        # merge all similar lines
        while not finished:
            finished = True
            for line in lines:
                for other in lines:
                    if line is not other:
                        if line.is_similar(other):
                            line.merge(other)
                            lines.remove(other)
                            finished = False

        return lines

    def shutdown(self):
        cv2.destroyAllWindows()

    def match_contour(self, combined_lines):
        succ_status = DETECTION_EDGECOUNT
        combined_lines_angles = self.calc_angles(combined_lines)

        combined_lines_length = []
        for line in combined_lines:
            length = abs(line.length())
            combined_lines_length.append(length)

        input_sum = sum(combined_lines_length)
        for i, l in enumerate(combined_lines_length):
            combined_lines_length[i] = l / input_sum

        for i, shape in enumerate(self.shape_collection.shapes):
            shape_length = shape.lengths

            orig_sum = sum(shape_length)
            for k, l in enumerate(shape_length):
                shape_length[k] = l / orig_sum

            match, start = self.compare_both(shape.angles, combined_lines_angles, shape_length, combined_lines_length)
            if match:
                return i, start, DETECTION_LENGTH
        return None, None, succ_status

    def match_color_triangle_simple(self, img, combined_lines, shape, start_index):
        p0 = combined_lines[(start_index + shape.triangle_start + 1) % len(combined_lines)].start
        p1 = combined_lines[(start_index + shape.triangle_start + 1) % len(combined_lines)].end
        p2 = combined_lines[(start_index + shape.triangle_start) % len(combined_lines)].start
        triangle = [(p0.x, p0.y), (p1.x, p1.y), (p2.x, p2.y)]

        poly = []
        for p in combined_lines:
            poly.append((p.start.x, p.start.y))

        hist_color_triangle = self.img_ops.calc_hist_in_area(img, triangle)
        hist_shape = self.img_ops.calc_hist_in_shape_outside_area(img, poly, triangle)
        color_match = self.img_ops.compare_hists(hist_color_triangle, hist_shape)
        print color_match
        if color_match < MAX_CORRELATION_COEFFICIENT:
            return True, DETECTION_COLOR_REST
        else:
            return False, DETECTION_LENGTH



    def match_color_triangle(self, img, combined_lines, shape, start_index, difficulty=0):
        p0 = combined_lines[(start_index + shape.triangle_start + 1) % len(combined_lines)].start
        p1 = combined_lines[(start_index + shape.triangle_start + 1) % len(combined_lines)].end
        p2 = combined_lines[(start_index + shape.triangle_start) % len(combined_lines)].start
        triangle = [(p0.x, p0.y), (p1.x, p1.y), (p2.x, p2.y)]

        hist_color_triangle = self.img_ops.calc_hist_in_area(img, triangle)
        color_match = self.img_ops.compare_hists(hist_color_triangle, self.color_calibration['color_triangle'])
        cv2.fillConvexPoly(img, np.array([triangle], 'int32'), (255, 0, 0))
        if color_match > MIN_CORRELATION_COEFFICIENT * (difficulty + 1):
            poly = []
            for p in combined_lines:
                poly.append((p.start.x, p.start.y))
            hist_shape = self.img_ops.calc_hist_in_shape_outside_area(img, poly, triangle)
            color_match = self.img_ops.compare_hists(hist_shape, self.color_calibration['background'])
            if color_match > MIN_CORRELATION_COEFFICIENT * (difficulty + 1):
                return True, DETECTION_COLOR_REST
            else:
                return False, DETECTION_COLOR_TRIANGLE
        return False, DETECTION_LENGTH