Я предлагаю другой подход. Таким образом, подход выглядит следующим образом (сделанный на скорую руку, возможно, потребуется некоторая настройка):
- Найти центр прямоугольника минимальной площади (повернутый прямоугольник), который охватывает всю стрелку. (Круг, нарисованный на третьем изображении)
- Найдите центр тяжести для всех белых точек. Он будет немного смещен в сторону фактического наконечника стрелки. (Нарисовано в 4-й пи c как источник собственного вектора)
- Найти собственные векторы для всех белых точек.
- Найти вектор смещения (центр тяжести - центр повернутого прямоугольника) )
Сейчас:
- Угол стрелки (неориентированный): угол первого собственного вектора
- Направление стрелки: знак точечного произведения 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: В это редактирование входят следующие изменения:
- Используйте cv2.fitLine () и используйте установленный угол линии для ориентации.
- Замените 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:
Первое изображение:
Второе изображение:
Третье изображение: