Контуры сортировки Python OpenCV по часовой стрелке

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

Я нашел эту статью, в которой описывается сортировка по горизонтали и вертикали:

https://www.pyimagesearch.com/2015/04/20/sorting-contours-using-python-and-opencv/

Кто-нибудь знает, как сортировать контуры по часовой стрелке?

Код ниже.

import os
import exifread
import cv2
import numpy as np
import scipy
from matplotlib import pyplot as plt
import imutils
import pandas as pd


#---------- INPUT ----------

# Define the image filename
img_filename = 'frame397.jpg'

img_path = img_filename

# Define values for cropping
x = 0
y = 200
w = 1200
h = 800

# Define color values for segmentation
# the values can be probed with GIMP

h1 = 0
s1 = 70
v1 = 120
h2 = 255
s2 = 255
v2 = 255

red_lower = np.array([h1,s1,v1])
red_upper = np.array([h2,s2,v2])

# Define desired area size
# desired area size is pixel count - use GIMP for probe
s1 = 500
s2 = 10000


#---------- PROCESS IMAGES ----------

# Create an empty dataframe for storing results
# in shape of (image_name,time,angle,angle_smooth,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11)

# Define the results dataframe shape and column names
results_df = pd.DataFrame(columns=['image_name','alpha','r1','r2','r3','r4','r5','r6','r7','r8','r9','r10','r11',
                                   'center_dist1', 'center_dist2','center_dist3','center_dist4',
                                   'center_dist5','center_dist6','center_dist7','center_dist8',
                                   'center_dist9','center_dist10','center_dist11'])

# Open image, make it black and white and find contours
img = cv2.imread(img_path)
crop = img[y:y+h, x:x+w]
blur = cv2.blur(crop,(2,2))
hsv = cv2.cvtColor(blur,cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, red_lower, red_upper)
mask_copy = mask.copy()
cnts = cv2.findContours(mask_copy,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
#print cnts
x = []
y = []

# Loop through contours, calculate the centers and prepare the
#contours and contour centers display

#define the font for the text on the image
font = cv2.FONT_HERSHEY_SIMPLEX

for cnt in cnts:
    area = cv2.contourArea(cnt)
    moment = cv2.moments(cnt)
    if s1<area<s2:
        print area
        c_x = int(moment["m10"]/moment["m00"])
        c_y = int(moment["m01"]/moment["m00"])
        #draw contours
        cv2.drawContours(crop, cnt, -1, (0,255,0),3)
        #draw a circle in the center of every contour, -1 is for thickness, this means
        #that the cirlce will get filled in
        cv2.circle(crop, (c_x,c_y), 10, (0,255,0),-1)
        #display center coordinates on the image
        string = str(c_x) + ',' + str(c_y)
        cv2.putText(crop,string,(c_x,c_y),font,0.5,(255,255,255),2)
        x.append(float(c_x))
        y.append(float(c_y))
        print (c_x, c_y)

print x
print y

# Display image
cv2.namedWindow('Contours', cv2.WINDOW_NORMAL)
cv2.resizeWindow('Contours', 1200,900)
cv2.imshow('Contours', crop)

# Wait for windows closing
cv2.waitKey() & 0xFF
cv2.destroyAllWindows

Изображение находится здесь: введите здесь описание изображения


person user2882635    schedule 04.02.2021    source источник


Ответы (1)


Я использовал minEnclosingCircle openCV, чтобы подогнать круг к точкам (на самом деле это не подходит, но достаточно хорошо для нахождения точки внутри кривизны маркеров). Пометка каждого контура углом от его центра тяжести до центра круга дала мне набор углов, с которыми я мог сортировать.

введите здесь описание изображения

import cv2
import numpy as np
import math

# 2d distance
def dist2D(one, two):
    dx = one[0] - two[0];
    dy = one[1] - two[1];
    return math.sqrt(dx*dx + dy*dy);

# angle between three points (the last point is the middle)
def angle3P(p1, p2, p3):
    # get distances
    a = dist2D(p3, p1);
    b = dist2D(p3, p2);
    c = dist2D(p1, p2);

    # calculate angle // assume a and b are nonzero
    # (law of cosines)
    numer = c**2 - a**2 - b**2;
    denom = -2 * a * b;
    if denom == 0:
        denom = 0.000001;
    rads = math.acos(numer / denom);
    degs = math.degrees(rads);

    # check if past 180 degrees
    if p1[1] > p3[1]:
        degs = 360 - degs;
    return degs;

# load image
img = cv2.imread("slinky.jpg");

# rescale
scale = 0.5;
h, w = img.shape[:2];
h = int(h * scale);
w = int(w * scale);
img = cv2.resize(img, (w,h));

# change color space
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB);
l,a,b = cv2.split(lab);

# threshold
thresh = cv2.inRange(a, 140, 255);

# get rid of little dots
kernel = np.ones((3,3),np.uint8)
thresh = cv2.erode(thresh,kernel,iterations = 1);
thresh = cv2.dilate(thresh,kernel, iterations = 1);

# contours
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);

# get centroids
centroids = [];
centers = [];
for con in contours:
    m = cv2.moments(con);
    cx = int(m['m10'] / m['m00']);
    cy = int(m['m01'] / m['m00']);
    centers.append([cx, cy]);
    centroids.append([[cx, cy], con]);
    img = cv2.circle(img, (cx, cy), 10, (0,0,255), -1);

# find circle around points
# NOTE: this doesn't "fit" a circle to the points
# I'm just using this to find a "good enough" center 
# that's in the direction of the curve
numped = np.array(centers);
(x, y), radius = cv2.minEnclosingCircle(numped);
img = cv2.circle(img, (int(x), int(y)), int(radius), (255,0,0), 2);
middle = [x,y];
offshoot = [x + 100, y];

# get angles
angles = [];
for cen in centroids:
    center, contour = cen;
    angle = angle3P(center, offshoot, middle);
    angles.append([angle, center, contour]);

# sort by angle
final = sorted(angles, key = lambda a: a[0], reverse = True);

# pull out just the contours
contours = [clump[2] for clump in final];

# draw contours in order
marked = img.copy();
counter = 0;
for con in contours:
    cv2.drawContours(marked, [con], -1, (0, 255, 0), 2);
    cv2.imshow("marked", marked);
    cv2.imwrite("marking_seq/" + str(counter) + ".png", marked);
    counter += 1;
    cv2.waitKey(0);

# show
cv2.imshow("orig", img);
cv2.imshow("a", a);
cv2.imshow("thresh", thresh);
cv2.waitKey(0);
person Ian Chu    schedule 04.02.2021
comment
Это отличный совет, и он работает! Несколько вопросов о других фрагментах кода: 1. Почему вы использовали цветовое пространство LAB? 2.Почему вы использовали cv2.RETR_TREE в findContours? 3. Какие маленькие точки, как вы упомянули, вам пришлось удалить? 4. Что такое комок в вытягивании контурной части кода? - person user2882635; 09.02.2021
comment
Я использовал цветовое пространство LAB, потому что мне не нравится находить красный цвет в HSV, так как красный обтекает от 0 до 180. Параметр cv2.RETR_TREE в findContours был полностью произвольным, не стесняйтесь использовать другой параметр, если хотите использовать другую иерархию. . Маленькие точки — это просто зашумленные биты в маске изображения. Обычно я выполняю операцию открытия, подобную этой, чтобы после порогового значения избавиться от крошечных спецификаций. Вы также можете отфильтровать их по размеру контура. Clump — это просто имя временной переменной в цикле for. Этот цикл for перебирает каждый элемент в финале. - person Ian Chu; 09.02.2021
comment
Для дальнейшего уточнения этой строки. final — это просто отсортированная копия списка углов. Каждый элемент представляет собой [угол, центр, контур]. Поэтому, когда он повторяется, как для clump в финале, каждый clump представляет собой список [угол, центр, контур], поэтому нам нужен clump[2]. - person Ian Chu; 09.02.2021
comment
если вы копируете этот код напрямую, вы, вероятно, также захотите закомментировать строку cv2.imwrite. Или вам нужно добавить папку marking_seq туда, откуда вы запускаете этот код. - person Ian Chu; 09.02.2021
comment
Хорошо, спасибо за пояснение. Я рассмотрю цветовое пространство LAB. - person user2882635; 10.02.2021
comment
Еще один вопрос (надеюсь не слишком тупой); в моем коде я сегментировал изображение, используя цветовое пространство HSV и определяя 2 массива numpy для нижнего и верхнего предела красного цвета. Вы использовали только одно значение в thresh части кода. Как добиться интервала подходящей цветовой гаммы? - person user2882635; 11.02.2021
comment
Я устанавливаю порог только для канала «а» цветового пространства лаборатории. Канал «а» отвечает за красный и зеленый цвета, поэтому красный цвет здесь отчетливо виден. Если вы просмотрите каждый канал отдельно как изображение в градациях серого, вы увидите, что красный выделяется на канале «а», поэтому вы можете просто использовать его и игнорировать другие каналы. - person Ian Chu; 11.02.2021