Я написал пример, в котором я:
- увеличиваю яркость изображения: умножаем изображение на
(255/img.max())
- применяем
threshold
для преобразования в двоичное изображение (черный и белый) - применить
erode
, чтобы стереть маленькие точки и тонкие белые элементы - применить
dilate
, чтобы уменьшить размер черных дыр
На этом этапе Вы можете использовать boundingRect
, если ваш интересующий объект - единственная белая область. К сожалению, на вашем изображении фон на левой стороне слишком яркий ... Тогда вы можете продолжить:
- , альтернативно, примените
Canny
- вместо порога / размыть / расширить - чтобы найти контуры и получить двоичное изображение - применить
findContours
к двоичному изображению (в приведенном ниже коде я избыточно использую изображение Canny-ed просто для демонстрации примера, но это может быть двоичное изображение с помощью threshold / erode / Расширьте, с похожими результатами) - опционально примените
approxPolyDP
и hull
для сглаживания контуров (в приведенном ниже примере я рисую результаты этих операций, но я не использую их на самом деле) - вычислить
contourArea
и boundingRect
для каждого контура - вернуть только контур, имеющий наибольшую площадь
Результат: (красный boundingRect
, зеленый approxPolyDP
и синий hull
)
Почти все эти операции имеют рабочие примеры в разделе учебника на сайте openCV .
Код
import cv2 as cv
import numpy as np
import random as rng
src = cv.imread("1KbH6.png")
src_gray = cv.cvtColor(src,cv.COLOR_BGR2GRAY)
# adjust brightness
src_bright = cv.convertScaleAbs(src_gray, alpha = 255.0/src.max(), beta = 0)
# apply threshold
threshold = 100
_, img_thresh = cv.threshold(src_bright, threshold, 255, 0)
# apply erode
erosion_size = 7
erosion_type = cv.MORPH_ELLIPSE
element = cv.getStructuringElement(erosion_type, (2*erosion_size + 1, 2*erosion_size+1), (erosion_size, erosion_size))
img_erosion = cv.erode(img_thresh, element)
# apply dilate
dilatation_size = 17
dilatation_type = cv.MORPH_ELLIPSE
element = cv.getStructuringElement(dilatation_type, (2*dilatation_size + 1, 2*dilatation_size+1), (dilatation_size, dilatation_size))
img_dilate = cv.dilate(img_erosion, element)
# apply canny and find contours
threshold = 100
canny_output = cv.Canny(img_dilate, threshold, threshold * 2)
contours = cv.findContours(canny_output, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# apply approxPolyDP, hull, boundingRect and calculate areas for each contour
contours_poly = [None]*len(contours[1])
boundRect = [None]*len(contours[1])
areas = [None]*len(contours[1])
hull_list = []
for i, c in enumerate(contours[1]):
contours_poly[i] = cv.approxPolyDP(c, 3, True)
hull_list.append(cv.convexHull(contours[1][i]))
boundRect[i] = cv.boundingRect(contours_poly[i])
areas[i] = cv.contourArea(c)
# set drawing
drawing = np.zeros((canny_output.shape[0], canny_output.shape[1], 3), dtype=np.uint8)
# draw only the contour with the greatest area
i = areas.index(max(areas))
color = (0,0,255)
cv.drawContours(drawing, contours_poly, i, (0,255,0),2)
cv.drawContours(drawing, hull_list, i, (255,0,0),2)
cv.rectangle(drawing, (int(boundRect[i][0]), int(boundRect[i][1])), \
(int(boundRect[i][0]+boundRect[i][2]), int(boundRect[i][1]+boundRect[i][3])), (0,0,255), 3)
# # Alternatively, you can draw contours with area bigger than some value
# for i in range(len(contours[1])):
# if areas[i] > 1000:
# color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256))
# cv.drawContours(drawing, contours_poly, i, color)
# cv.drawContours(drawing, hull_list, i, color)
# cv.rectangle(drawing, (int(boundRect[i][0]), int(boundRect[i][1])), \
# (int(boundRect[i][0]+boundRect[i][2]), int(boundRect[i][1]+boundRect[i][3])), color, 2)
cv.imwrite('new.png',cv.add(drawing,src))
cv.imshow('blend',cv.add(drawing,src))
cv.waitKey()
# # If you want to see partial results of image processing
# cv.imshow('src', src)
# cv.imshow('src_bright', src_bright)
# cv.imshow('img_thresh', img_thresh)
# cv.imshow('img_erosion', img_erosion)
# cv.imshow('img_dilate', img_dilate)
# cv.imshow('canny_output', canny_output)
# cv.imshow('drawing',drawing)
Альтернатива: Chroma ke y
Еще один хороший способ улучшить обнаружение - использовать что-то вроде хроматического ключа: выберите определенный c цвет фона, преобразуйте цветное изображение из RGB в HSV и создайте маску (двоичное изображение) с фильтрацией по оттенку. Вот пример: https://docs.opencv.org/master/da/d97/tutorial_threshold_inRange.html