Измерение угла кривой проволоки с помощью OpenCV

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

Я новичок в обработке изображений, поэтому извините за грубую терминологию.

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

Вам следует подумать (и представить): «Насколько вы можете упростить проблему?». Если вы можете установить некоторые предварительные условия относительно входного изображения (например, условия о шуме, фоне, проводе {цвет, толщина, расположение, форма, существование} и т. д.), программа может зависеть от них. Без каких-либо предварительных условий это означает «Любое входное изображение», и задача будет очень и очень сложной.

fana 22.05.2024 04:32

Спасибо за ответ, только что обновил образец фото, провод черный, а фон белый.

naro 22.05.2024 04:56

необходим подход «машинного зрения». разверните несколько линий отбора проб, чтобы найти участки интересующей вас детали.

Christoph Rackwitz 22.05.2024 07:18

Я опубликую ответ сегодня вечером... Я буду рад еще одной или двум фотографиям, показывающим ожидаемое изменение измеряемого объекта (перемещение, вращение, угол изгиба и т. д.).

Christoph Rackwitz 22.05.2024 09:16
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
4
153
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я бы попробовал следующие шаги:

  1. Скелетируйте образ.
  2. Получите соответствующие очки на пути скелета.
  3. Вычислите угол.

Обновлено:

import cv2
import numpy as np
from skimage import morphology, graph
from skan import Skeleton

def angle(v1, v2):
    rad = np.arctan2(v2[0], v2[1]) - np.arctan2(v1[0], v1[1])
    return np.abs((np.rad2deg(rad)))

# read an threshold image
img = cv2.imread('img.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
inverted = 255 - gray
ret, thresh = cv2.threshold(inverted, 50, 255, cv2.THRESH_BINARY)

# skeletonize image and split skeleton to paths, for now assume there is only one
skeleton = morphology.skeletonize(thresh, method='lee')
g = Skeleton(skeleton)
path = np.array(g.path_coordinates(0).astype(int))

# draw path for demonstration purpose
for p in path:
    img[p[0], p[1]] = [0, 0, 255]

# get reference points by percental position on path
l = len(path)
a1 = path[int(0.02 * l)]
a2 = path[int(0.10 * l)]
b1 = path[int(0.98 * l)]
b2 = path[int(0.90 * l)]

# results
print(f"Degrees: {angle(a1 - a2, b1 - b2):0.2f}")
cv2.line(img, (a1[1], a1[0]), (a2[1], a2[0]), [0, 255, 0], 2)
cv2.line(img, (b1[1], b1[0]), (b2[1], b2[0]), [0, 255, 0], 2)
cv2.imwrite("out.png", img)

Выход:

Degrees: 32.32

Было бы лучше ответить, если бы вы показали код и вывод вашего кода.

fmw42 22.05.2024 18:45

Большое спасибо за Вашу помощь. Я попробую решение, которое вы сказали.

naro 23.05.2024 09:22

интересный подход!

Christoph Rackwitz 23.05.2024 14:41

Я предполагаю, что заготовка всегда находится примерно в одном и том же положении, с некоторым пространством для маневра.

Этот подход склоняется к «машинному зрению», то есть типам операций, которые вы получаете от коммерческих программ MV, предназначенных для автоматического оптического контроля. Эти программы позволяют «проводить» измерения визуально и в интерактивном режиме. Это избавляет от множества проб и ошибок и кодирования.

Я вручную выбрал области, в которых будет измеряться изогнутая проволока. Для каждой области я определил две конечные точки, а затем объявил пять пересекающихся линий по длине, по которым я буду сканировать изображение.

Эти линии не обязательно должны быть прямыми. Имея больше вуду, вы можете легко создать для этого дуги, скажем, можно ли более или менее согнуть конец. cv.polarToCart может в этом помочь.

Это образцы высотой 5 пикселей и шириной 101 пиксель, увеличенные, первая левая строка:

(А это из концентрических дуг, наизнанку, а лево есть право... эх, просто вопрос знаков)

Для каждой из выбранных линий я вычисляю градиент и нахожу там минимум и максимум. Это должны быть края заготовки по каждой линии.

Вот график одного из них с градиентом:

А вот точки на картинке, соответствующие этим экстремумам:

Затем я беру середину каждого из них и прокладываю линию.

Тогда угол между обеими линиями (их векторами) составляет около ~30 градусов.


Здесь есть некоторые крайние случаи, например, что происходит, если линия отбора проб не пересекает заготовку, как я спровоцировал на кончике «рабочего конца». Вы можете увидеть, насколько это незначительно, на выборках размером 101 x 5 пикселей.

Такие случаи можно решить, оценив величину экстремумов и отбросив линию, если она слишком мала.


Код скоро появится...

import numpy as np
from numpy.linalg import norm, inv
import cv2 as cv

# utility functions

def vec(*a):
    if len(a) == 1:
        (a,) = a
    return np.asarray(a)

def normalize(v):
    return np.asarray(v) / norm(v)

def translate2(dx=0, dy=0):
    T = np.eye(3)
    T[0:2, 2] = [dx, dy]
    return T

def scale2(s=1, sx=1, sy=1):
    return np.diag([s*sx, s*sy, 1])

def normal2(v):
    x,y = v
    return np.asarray([-y, +x])

def to_basis(origin, ax, ay):
    T = np.eye(3)
    T[0:2, 0] = ax
    T[0:2, 1] = ay
    T[0:2, 2] = origin
    return T

def index_grid(nx, ny=1):
    return np.mgrid[0:ny, 0:nx].transpose(1, 2, 0)[:,:,::-1]

def transform_grid(H, grid):
    (h, w, inner_dim) = grid.shape
    assert inner_dim == 2
    output_points = cv.transform(
        src=grid.reshape( (-1, 1, inner_dim) ).astype(np.float32),
        m=H)
    # hoping [..., 2] is all ones
    output_points = output_points[..., :-1]
    output_points.shape = (h, w, inner_dim)
    return output_points

def grid_along_line(p0, p1, width, nx, ny):
    v0 = p1 - p0
    length = norm(v0)
    v0 = v0 / length
    dx = width / (nx-1)
    dy = length / (ny-1)
    grid = index_grid(nx, ny)
    T = scale2(sx=dx, sy=dy) @ translate2(-(nx-1)/2, 0)
    T = to_basis(p0, -normal2(v0), v0) @ T
    grid = transform_grid(T, grid)
    return T, grid

def get_midpoints(sampled, T):
    midpoints = []

    for i, row in enumerate(sampled):
        grad = np.gradient(row)

        jrising = np.argmax(grad)
        jfalling = np.argmin(grad)

        # vrising = grad[jrising]
        # vfalling = grad[jfalling]
        # print(vrising - vfalling)

        p_rising = T @ vec(jrising, i, 1)
        p_falling = T @ vec(jfalling, i, 1)
        p_mid = (p_rising + p_falling) / 2

        midpoints.append(p_mid[:2])

    return np.asarray(midpoints)

im = 255 - cv.imread("JqI70V2C.png", cv.IMREAD_GRAYSCALE)
(height, width) = im.shape[:2]

# manually defined bands across which to take sample lines

working_end_p0 = vec(376, 315)
working_end_p1 = vec(585, 181)
T_working_end, working_end_grid = grid_along_line(working_end_p0, working_end_p1, 200.0, 101, 5)

standing_end_p0 = vec(4, 505)
standing_end_p1 = vec(678, 487)
T_standing_end, standing_end_grid = grid_along_line(standing_end_p0, standing_end_p1, 200.0, 101, 5)

working_end_sampled = cv.remap(im, map1=working_end_grid, map2=None, interpolation=cv.INTER_LANCZOS4)
standing_end_sampled = cv.remap(im, map1=standing_end_grid, map2=None, interpolation=cv.INTER_LANCZOS4)

vecs = []
for (sampled, T) in [
        (working_end_sampled, T_working_end),
        (standing_end_sampled, T_standing_end)
    ]:
    points = get_midpoints(sampled, T)

    cov = np.cov(points.T)

    (eigenvalues, eigenvectors) = np.linalg.eig(cov) # column vectors

    major_axis = eigenvectors[:, np.argmax(eigenvalues)]

    x_ = points[:,0].mean()
    y_ = points[:,1].mean()
    p0 = vec(x_, y_)
    v = normalize(vec(major_axis))

    vecs.append(v)


[v1, v2] = vecs
angle = np.arccos(v1 @ v2)
print(f"{np.degrees(angle):.1f} deg")

Я пропустил визуализации, потому что они довольно тривиальны (вызов circle() для каждой точки). В последнем цикле вы можете рисовать линии следующим образом:

canvas = cv.cvtColor(im, cv.COLOR_GRAY2BGR)

# draw major axis
cv.line(
    img=canvas,
    pt1=(p0 - 1000*v).round().astype(int),
    pt2=(p0 + 1000*v).round().astype(int),
    color=(0, 255, 0),
    thickness=2)

если у вас есть дополнительные вопросы, пожалуйста, дайте мне знать. Если этот ответ решит вашу проблему, вы можете сообщить об этом людям, отметив его галочкой как «принятый». цель этого представлена ​​в туре по сайту.

Christoph Rackwitz 23.05.2024 10:22

Точки «work_end_p0», «work_end_p1», «standing_end_p0», «standing_end_p1» берутся вручную. Есть ли способ автоматически определить эти точки?

naro 23.05.2024 10:51

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

Christoph Rackwitz 23.05.2024 11:37

Спасибо @Christoph Rackwitz. Я буду обновлять новые фотографии.

naro 24.05.2024 02:27

Другие вопросы по теме