Нахождение характеристик рукописных стрелок с opencv - PullRequest
2 голосов
/ 27 марта 2020

Я пытаюсь восстановить ориентацию рукописных стрелок: после удаления теней, применения бинаризации и расширения линий, вот изображения:

enter image description here enter image description here enter image description here

Теперь я хотел бы получить ориентацию стрелки, поэтому я попытался использовать HoughLines,

lines = cv2.HoughLines(edges, rho=1, theta=np.pi / 180, threshold=20) 

Но, похоже, он генерирует слишком много строк (около 54 строк), я хотел бы, чтобы он генерировал только 3 строки, чтобы я мог найти пересечение этих линий. Я могу сгруппировать линии в группы с одинаковым углом (+/- 20 градусов) и затем усреднить угол. но я не уверен, что должно быть rho средней линии, кто-нибудь может привести простой пример?

Есть ли другой подход, который может быть более точным?

Буду рад услышать, спасибо всем

1 Ответ

3 голосов
/ 27 марта 2020

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

  1. Найти центр прямоугольника минимальной площади (повернутый прямоугольник), который охватывает всю стрелку. (Круг, нарисованный на третьем изображении)
  2. Найдите центр тяжести для всех белых точек. Он будет немного смещен в сторону фактического наконечника стрелки. (Нарисовано в 4-й пи c как источник собственного вектора)
  3. Найти собственные векторы для всех белых точек.
  4. Найти вектор смещения (центр тяжести - центр повернутого прямоугольника) )

Сейчас:

  1. Угол стрелки (неориентированный): угол первого собственного вектора
  2. Направление стрелки: знак точечного произведения of (первый собственный вектор и вектор смещения центров)

Код:

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

angle = (angle - math.pi) * 180 / math.pi
return angle, (mean[0,0]), (mean[0,1]), p1

Код, реализующий логи c выше:

#threshold
_, img = cv2.threshold(img, 128, 255, cv2.THRESH_OTSU)
imshow(img)

#close the image to make sure the contour is connected)
st_el = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, st_el)
imshow(img)

#get white points
pnts = cv2.findNonZero(img)

#min area rect
rect_center = cv2.minAreaRect(pnts)[0]

#draw rect center
cv2.circle(img, (int(rect_center[0]), int(rect_center[1])), 3, 128, -1)
imshow(img)

angle, pca_center, eigen_vec = getOrientation(pnts, img)

cc_vec = (rect_center[0] - pca_center[0], rect_center[1] - pca_center[1])
dot_product = cc_vec[0] * eigen_vec[0] + cc_vec[1] * eigen_vec[1]
if dot_product > 0:
    angle *= -1

print ("Angle = ", angle)
imshow(img)

Редактировать

Я предлагаю более простой метод. Этот новый метод не зависит от PCA для определения неориентированного угла [0 - 180]. Вместо этого сразу используется минимальный угол прямоугольника. И использует контурный импульс для нахождения центра тяжести.

Более простой код метода:

#get white points
pnts = cv2.findNonZero(img)

#min area rect
rect_center, size,  angle = cv2.minAreaRect(pnts)

#simple fix for angle to make it in [0, 180]
angle = abs(angle)
if size[0] < size[1]:
    angle += 90

#find center of gravity    
M = cv2.moments(img)
gravity_center = (M["m10"] / M["m00"], M["m01"] / M["m00"])

#rot rect vec based on angle
angle_unit_vec = (math.cos(angle * 180 / math.pi), math.sin(angle * 180 / math.pi))

#cc_vec = gravity center - rect center
cc_vec = (gravity_center[0] - rect_center[0], gravity_center[1] - rect_center[1])

#if dot product is negative add 180 -> angle between [0, 360]
dot_product = cc_vec[0] * angle_unit_vec[0] + cc_vec[1] * angle_unit_vec[1]
angle += (dot_product < 0) * 180

#draw rect center
cv2.circle(img, (int(rect_center[0]), int(rect_center[1])), 3, 128, -1)
cv2.circle(img, (int(gravity_center[0]), int(gravity_center[1])), 3, 20, -1)

imshow(img)
print ("Angle = ", angle)

Редактировать2: В это редактирование входят следующие изменения:

  1. Используйте cv2.fitLine () и используйте установленный угол линии для ориентации.
  2. Замените angle_unit_ve c на вектор с центром тяжести в качестве начала координат, идущий параллельно подобранной линии.

Код

#get white points
pnts = cv2.findNonZero(img)
#min area rect
rect_center, size,  angle = cv2.minAreaRect(pnts)

#fit line to get angle
[vx, vy, x, y] =cv2.fitLine(pnts, cv2.DIST_L12, 0, 0.01, 0.01)
angle = (math.atan2(vy, -vx)) * 180 / math.pi

M = cv2.moments(img)
gravity_center = (M["m10"] / M["m00"], M["m01"] / M["m00"])

angle_vec = (int(gravity_center[0] + 100 * vx), int(gravity_center[1] + 100 * vy))

#cc_vec = gravity center - rect center
cc_vec = (gravity_center[0] - rect_center[0], gravity_center[1] - rect_center[1])

#if dot product is positive add 180 -> angle between [0, 360]
dot_product = cc_vec[0] * angle_vec[0] + cc_vec[1] * angle_vec[1]
angle += (dot_product > 0) * 180


angle += (angle < 0) * 360


#draw rect center
cv2.circle(img, (int(rect_center[0]), int(rect_center[1])), 3, 128, -1)
cv2.circle(img, (int(gravity_center[0]), int(gravity_center[1])), 3, 20, -1)

imshow(img)
print ("Angle = ", angle)

Вывод: Использование кода из edit2:

Первое изображение:

enter image description here

Второе изображение:

enter image description here

Третье изображение:

enter image description here

...