Нахождение минимально необходимых точек для представления полилинии из n точек в python - PullRequest
1 голос
/ 31 января 2020

У меня есть двоичное изображение, которое показывает белую линию на изображении. У меня есть все точки координат белых пикселей со мной. Я хочу найти минимальные точки, необходимые для представления этой полилинии из этих n точек. Как я могу это сделать? Пожалуйста, помогите мне.

enter image description here

Как вы можете видеть на изображении, чтобы представить это V изображение, нам просто нужно три точки. Но я получил координаты всех белых пикселей. Итак, как я могу получить минимальные баллы за представление таких изображений? Это образец изображения. Изображения могут варьироваться до изогнутой линии. Пожалуйста, помогите мне с этой проблемой.

Ответы [ 2 ]

1 голос
/ 01 февраля 2020

Спасибо всем за проявленный интерес к моему вопросу. К счастью, я получил решение. Python имеет библиотеку rdp для этого. rdp Таким образом, эта библиотека даст минимальные точки для представления линии, в которой мы можем указать допуск (здесь он называется epsilon).

minPoints = rdp(whitePixelArray,epsilon=1)
print(minPoints)

Здесь whitePixelArray имеет координаты всех белые пиксели на моем изображении. мы просто вызываем rdp с epsilon = 1, а результирующие minPoints имеют только 6 точек.

This is the output in the python shell

Как вы можете видеть на рисунке выше, мой массив input и массив ниже - это результирующий массив после операции rdp. Обратите внимание, что эти координаты не относятся к изображению, которое я разместил в своем вопросе (извините за это).

0 голосов
/ 31 января 2020

Это невероятный хакерский ответ, я приветствую любые лучшие решения

Итак, мы начнем с этого изображения:

White V on black background

Я читаю этот файл и получаю все белые пиксели, используя:

from PIL import Image

im = Image.open('image.png')
pix = im.load()
width,height = im.size
values = []
for w in range(width):
    for h in range(height):
        if sum(pix[w, h]) > 255 * 1.5:
            values.append((width - w, height - h))

X = [[i[0]] for i in values]
y = [[i[1]] for i in values]

Рассеяние это приводит к:

import matplotlib.pyplot as plt

plt.scatter(X, y)
plt.show()

Blue V on white background

Теперь идет много кода, который я написал для другого проекта, я не буду объяснять, как он работает, но вы можете найти оригинальный код здесь :

from sklearn.linear_model import LinearRegression
from numpy import median, array


class DecisionTreeError(Exception):
    pass


class DecisionTreeLinearNodes:

    def __init__(self):
        self.decisions = None

    def fit(self, X, y, min_R2):
        self.decisions = {}
        X = array(X)
        y = array(y)
        self._fit(X=X, y=y, min_R2=min_R2, decisions=[])

    def _fit(self, X, y, min_R2, decisions):

        clf = LinearRegression()
        clf.fit(X, y)

        if clf.score(X, y) > min_R2 or len(X) <= 3:

            self._add_decisions(decisions, (X, y))
        else:
            try:
                x_1, x_2, y_1, y_2, split_param = self._split(X, y)
                if min(len(x_1), len(x_2)) == 0:
                    raise DecisionTreeError
                print(split_param, len(x_1), len(x_2))
                self._fit(X=x_1, y=y_1, min_R2=min_R2, decisions=decisions + [(split_param, True)])
                self._fit(X=x_2, y=y_2, min_R2=min_R2, decisions=decisions + [(split_param, False)])
            except DecisionTreeError:
                print("Couldn't find a better fit")
                self._add_decisions(decisions, clf)

    def _split(self, X, y):
        best_r2 = 0
        best_param = None
        best_val = None
        for split_param in range(len(X[0])):
            lst = X[:, split_param]
            val = self._get_val(X, y, lst)
            if val is None:
                continue

            choices = lst >= val
            not_choices = lst < val

            x_1 = X[choices]
            y_1 = y[choices]
            x_2 = X[not_choices]
            y_2 = y[not_choices]

            if len(x_1) > 0:
                l1 = LinearRegression()
                l1.fit(x_1, y_1)
                acc1 = l1.score(x_1, y_1)
            else:
                acc1 = 0
            if len(x_2) > 0:
                l2 = LinearRegression()
                l2.fit(x_2, y_2)
                acc2 = l2.score(x_2, y_2)
            else:
                acc2 = 0

            acc = max(acc1, acc2)
            if acc > best_r2:
                best_r2 = acc
                best_param = split_param
                best_val = val

        if best_param is None:
            raise DecisionTreeError("No param fit")

        indexes = X[:, best_param] >= best_val
        indexes_false = X[:, best_param] < best_val
        return X[indexes], X[indexes_false], y[indexes], y[indexes_false], (best_param, best_val)

    def _get_val(self, X, y, lst):
        if len(set(lst)) > 2:
            val_guess_1 = median(lst)
            choices = lst >= val_guess_1
            not_choices = lst < val_guess_1

            x_1 = X[choices]
            y_1 = y[choices]
            x_2 = X[not_choices]
            y_2 = y[not_choices]

            l1 = LinearRegression()
            l1.fit(x_1, y_1)
            l2 = LinearRegression()
            l2.fit(x_2, y_2)

            predictions1 = l1.predict(X)
            predictions2 = l2.predict(X)

            errors1 = (y - predictions1) ** 2
            errors2 = (y - predictions2) ** 2

            prefered_choice = [0 if errors1[i] < errors2[i] else 1 for i in range(len(errors1))]
            lst_vals = sorted(list(zip(lst, prefered_choice)))
            total_1s = sum(prefered_choice)
            total_0s = len(prefered_choice) - total_1s
            seen_0s = 0
            seen_1s = 0
            val = 0
            best_score = 0

            for i in lst_vals:
                if i[1] == 0:
                    seen_0s += 1
                else:
                    seen_1s += 1

                score = seen_1s + total_0s - seen_0s
                if score > best_score:
                    best_score = score
                    val = i[0]

            if val == min(lst) or max(lst):
                return val_guess_1

            return val

        elif len(set(lst)) == 2:
            return (min(lst) + max(lst)) * 0.5
        else:
            return None

    def _add_decisions(self, decisions, clf):
        decisions_dict = self.decisions
        for decision, bool in decisions[:-1]:
            if decision not in decisions_dict:
                decisions_dict[decision] = {True: {}, False: {}}
            decisions_dict = decisions_dict[decision][bool]
        if len(decisions) > 0:
            decision, bool = decisions[-1]
            if decision not in decisions_dict:
                decisions_dict[decision] = {True: {}, False: {}}
            decisions_dict[decision][bool] = clf
        else:
            self.decisions = clf

Теперь, используя это, мы можем сгруппировать код в прямые линии:

decision_tree = DecisionTreeLinearNodes()
decision_tree.fit(X, y, min_R2=0.6)

Я использовал 0,6, потому что это, похоже, сработало, возможно, вам придется настроить это. Чтобы получить группы, в которых дерево группирует их, я использовал:

def flatten_array(array):
    x = [i[0] for i in array[0]]
    y = [i[0] for i in array[1]]
    return sorted(list(zip(x, y)))

def get_arrays(decision_tree):
    arrays = []
    for j in decision_tree.values():
        for i in [True, False]:
            if isinstance(j[i], tuple):
                arrays.append(flatten_array(j[i]))
            else:
                arrays += get_arrays(j[i])
    return arrays

arrays = get_arrays(decision_tree.decisions)

Я предупреждал вас, что это взломано;)

Теперь мы можем получить список точек которые описывают каждую из групп:

points = []

for i in arrays:
    points.append(i[0])
    points.append(i[-1])

Однако, если вы сделаете это, у вас будут «лишние» точки, где одна группа начинается, а другая заканчивается. Вы можете удалить их, используя (еще одно хакерское решение):

def get_distance(point1, point2):
    return ((point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2)**0.5

def remove_unnecesary_points(points, min_distance):
    to_delete = []
    for i in range(len(points)):
        for j in range(i+1, len(points)):
            distance = get_distance(points[i], points[j])
            if distance <= min_distance:
                to_delete.append(j)
    return [points[i] for i in range(len(points)) if i not in to_delete]

points = remove_unnecesary_points(points, 100)

Я использовал 100, потому что 80 кажется толщиной вашей линии, поэтому этого должно быть достаточно для сценария наихудшего случая. Теперь зарисовываем это:

for i in arrays:
    plt.scatter([j[0] for j in i], [j[1] for j in i])

plt.scatter([i[0] for i in points], [i[1] for i in points])
plt.show()

Результат:

V with orange and Blue, Green dots on the corners, white background

Здесь оранжевый - одна группа, синий - другая, а зеленые точки показать три очка. Чтобы наконец набрать число необходимых очков, используйте:

print(len(points))
>>> 3
...