Задача:
Найти жёсткий диск на фото, определить его угол и контуры
Проблема:
Не всегда удаётся найти правильный контур диска.
В коде я делаю изображения серыми, блюрю, нахожу разницу между ними и определяю контуры, но решение не точное. Хотелось бы вашей помощи.
firstFrame = cv2.imread(bg_im) # Фоновое изображение без диска
frame_img = cv2.imread(frame) # Изображение с диском
gray = cv2.cvtColor(frame_img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (21, 21), 0)
firstFrame = cv2.cvtColor(firstFrame, cv2.COLOR_BGR2GRAY)
firstFrame = cv2.GaussianBlur(firstFrame, (21, 21), 0)
frameDelta = cv2.absdiff(firstFrame, gray)
thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH_BINARY)[1]
thresh = cv2.dilate(thresh, None, iterations=2)
(cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
c = sorted(cnts, key = cv2.contourArea, reverse = True)[0]
rect = cv2.minAreaRect(c)
box = cv2.cv.BoxPoints(rect)
x1 = box[0][0]
x2 = box[1][0]
x4 = box[3][0]
y1 = box[0][1]
y2 = box[1][1]
y4 = box[3][1]
perimeter_1_4 = sqrt((x4 - x1)**2 + (y1 - y4)**2)
perimeter_1_2 = sqrt((x2 - x1)**2 + (y1 - y2)**2)
box = np.int0(box)
corner = list(rect)[2]
if perimeter_1_4 > perimeter_1_2:
corner = -(90 - corner)
cv2.drawContours(frame_img,[box],0,(0,0,255),2)
Ответ
Как только речь заходит о применении размытия с большим размером ядра, то, к сожалению, получается необратимая деформация объекта интереса. Объект контурами вроде и выделен, но при этом по своим размерам, форме (а подчас и положению) может иметь значительное расхождение с оригиналом. Стоит ли говорить, что морфологические операции (эрозия, дилатация), наложенные поверх, лишь усугубляют проблему.
В ситуации с представленными изображениями сложность возникает ещё и от того, что лента конвейера имеет весьма различимые следы производственного пользования. Это царапины, пятна и прочие элементы подобного рода, которые на фоновом изображении и изображении с объектом интереса имеют разное положение, да и вообще в деталях различаются значительно. Этот момент становится принципиальным, когда производится попиксельное вычитание одного изображения из другого.
В целом задача сводится к тому, чтобы не повредив сам объект, попытаться выделить его на общем фоне. Попробуем пойти от обратного и вместо того, чтобы применять размытие повысим резкость краёв.
void subtractPyr(const cv::Mat &src_mat, cv::Mat &dst_mat, int iterations) {
std::vector sizes(iterations);
cv::Mat mat = src_mat.clone();
for(int i = 0; i < iterations; ++i) {
sizes[iterations-(i+1)] = mat.size();
cv::pyrDown(mat, mat);
}
for(int i = 0; i < iterations; ++i)
cv::pyrUp(mat, mat, sizes.at(i));
cv::subtract(mat, src_mat, dst_mat);
}
cv::Mat src1_mat = cv::imread("foreground.jpg");
cv::Mat src2_mat = cv::imread("background.jpg");
cv::Mat gry1_mat, gry2_mat;
cv::cvtColor(src1_mat, gry1_mat, cv::COLOR_BGR2GRAY);
cv::cvtColor(src2_mat, gry2_mat, cv::COLOR_BGR2GRAY);
subtractPyr(gry1_mat, gry1_mat, 5);
subtractPyr(gry2_mat, gry2_mat, 5);
Изображение с объектом интереса:
Фоновое изображение:
Несмотря на то, что фоновое изображение имеет свой набор шумов, тем не менее оно содержит и те детали, которые полезно вычесть из изображения с объектом интереса. После этого можно выполнить бинаризацию.
cv::Mat gry_mat = gry1_mat - gry2_mat;
cv::Mat bin_mat;
cv::threshold(gry_mat, bin_mat, 1, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
Получим следующую маску:
Здесь хорошо заметна одна из проблем, связанная с неверно подобранным освещением. Левый верхний угол объекта интереса частично растворён, что не позволяет произвести поиск контура, опоясывающего объект, с корректным результатом.
Так или иначе продолжим именно через поиск контуров и отсеивание их по площади с эмпирически подобранным размером. Их иерархия больше не потребуется, а значит объединим все точки в один единственный большой контур.
std::vector > contours;
cv::findContours(bin_mat.clone(), contours
, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
std::vector big_contour;
for(auto itr = contours.begin(); itr != contours.end(); ++itr) {
if(cv::contourArea(*itr) > 300.) {
const std::vector &contour = *itr;
for(auto itr2 = contour.begin(); itr2 != contour.end(); ++itr2) {
big_contour.push_back(*itr2);
}
}
}
Если нарисовать большой контур, то можно увидеть, что лишние детали на изображении более не захватываются, а сам он в целом лежит внутри границ объекта интереса.
Остаётся получить опоясывающий объект интереса прямоугольник.
cv::RotatedRect rc = cv::minAreaRect(big_contour);
cv::Point2f points[4];
rc.points(points);
// Нарисуем его.
for(int i = 0; i < 4; ++i)
cv::line(dst_mat, points[i], points[(i+1)%4], cv::Scalar(0,0,255));
Как хорошо видно на следующем изображении полученный прямоугольник совершенно не идеально описывает объект интереса. Эта проблема связана с оптической деформацией этого самого объекта, а также с наличием довольно широкой тени, присутствующей с левой его стороны. К сожалению, эти проблемы нельзя устранить иначе, кроме как подстройкой объектива камеры и освещения.