Я ищу правильное решение, как считать частицы и измерять их размеры на этом изображении:
В итоге мне нужно получить списки координат частиц и площадей. После некоторого поиска в Интернете я понял, что есть 3 подхода к обнаружению частиц:
Глядя на разные проекты, я собрал некоторый код, смешав его.
import pylab
import cv2
import numpy as np
Размытие по Гауссу и пороговое значение
original_image = cv2.imread(img_path)
img = original_image
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.GaussianBlur(img, (5, 5), 0)
img = cv2.blur(img, (5, 5))
img = cv2.medianBlur(img, 5)
img = cv2.bilateralFilter(img, 6, 50, 50)
max_value = 255
adaptive_method = cv2.ADAPTIVE_THRESH_GAUSSIAN_C
threshold_type = cv2.THRESH_BINARY
block_size = 11
img_thresholded = cv2.adaptiveThreshold(img, max_value, adaptive_method, threshold_type, block_size, -3)
фильтровать мелкие объекты
min_size = 4
nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(img, connectivity=8)
sizes = stats[1:, -1]
nb_components = nb_components - 1
# for every component in the image, you keep it only if it's above min_size
for i in range(0, nb_components):
if sizes[i] < min_size:
img[output == i + 1] = 0
генерация контуров для заполнения отверстий и измерений. pos_list
и size_list
это то, что мы искали
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
pos_list = []
size_list = []
for i in range(len(contours)):
area = cv2.contourArea(contours[i])
size_list.append(area)
(x, y), radius = cv2.minEnclosingCircle(contours[i])
pos_list.append((int(x), int(y)))
для самопроверки, если мы нанесем эти координаты на исходное изображение
pts = np.array(pos_list)
pylab.figure(0)
pylab.imshow(original_image)
pylab.scatter(pts[:, 0], pts[:, 1], marker = "x", color = "green", s=5, linewidths=1)
pylab.show()
Мы могли бы получить что-то вроде следующего:
И... я не очень доволен результатами. Некоторые хорошо видимые частицы не включены, с другой стороны, были подсчитаны некоторые сомнительные флуктуации интенсивности. Играюсь сейчас с разными настройками фильтров, но ощущение, что это неправильно.
Если кто-то знает, как улучшить мое решение, поделитесь, пожалуйста.
Что, если мы применим порог перед использованием k-средних?
@WilliamDavis, вы можете применить порог до k-средних, но вы можете потерять точность контура. Причина, по которой я бы порекомендовал сначала k-средние, заключается в том, что они сегментируют изображение на основные цвета, поскольку частицы могут иметь разные оттенки серого/черного. Пороговое значение сначала потеряет детали, но это зависит от вас
Поскольку частицы белые, а фон черный, мы можем использовать квантование цвета Kmeans, чтобы разделить изображение на две группы с помощью cluster=2
. Это позволит нам легко различать частицы и фон. Поскольку частицы могут быть очень маленькими, мы должны стараться избегать размытия, расширения или любых морфологических операций, которые могут изменить контуры частиц. Вот подход:
K означает квантование цвета. Мы выполняем Kmeans с двумя кластерами, оттенками серого, затем порогом Оцу, чтобы получить бинарное изображение.
Отфильтруйте супер крошечный шум. Затем мы находим контуры, удаляем крошечные частицы шума с помощью фильтрации области контура и собираем координаты каждой частицы (x, y)
и ее площадь. Мы удаляем крошечные частицы на бинарной маске, «заполняя» эти контуры, чтобы эффективно стереть их.
Примените маску к исходному изображению. Теперь мы применяем побитовую и отфильтрованную маску к исходному изображению, чтобы выделить кластеры частиц.
Kзначает с clusters=2
Результат
Number of particles: 204
Average particle size: 30.537
Код
import cv2
import numpy as np
import pylab
# Kmeans
def kmeans_color_quantization(image, clusters=8, rounds=1):
h, w = image.shape[:2]
samples = np.zeros([h*w,3], dtype=np.float32)
count = 0
for x in range(h):
for y in range(w):
samples[count] = image[x][y]
count += 1
compactness, labels, centers = cv2.kmeans(samples,
clusters,
None,
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10000, 0.0001),
rounds,
cv2.KMEANS_RANDOM_CENTERS)
centers = np.uint8(centers)
res = centers[labels.flatten()]
return res.reshape((image.shape))
# Load image
image = cv2.imread('1.png')
original = image.copy()
# Perform kmeans color segmentation, grayscale, Otsu's threshold
kmeans = kmeans_color_quantization(image, clusters=2)
gray = cv2.cvtColor(kmeans, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Find contours, remove tiny specs using contour area filtering, gather points
points_list = []
size_list = []
cnts, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2:]
AREA_THRESHOLD = 2
for c in cnts:
area = cv2.contourArea(c)
if area < AREA_THRESHOLD:
cv2.drawContours(thresh, [c], -1, 0, -1)
else:
(x, y), radius = cv2.minEnclosingCircle(c)
points_list.append((int(x), int(y)))
size_list.append(area)
# Apply mask onto original image
result = cv2.bitwise_and(original, original, mask=thresh)
result[thresh==255] = (36,255,12)
# Overlay on original
original[thresh==255] = (36,255,12)
print("Number of particles: {}".format(len(points_list)))
print("Average particle size: {:.3f}".format(sum(size_list)/len(size_list)))
# Display
cv2.imshow('kmeans', kmeans)
cv2.imshow('original', original)
cv2.imshow('thresh', thresh)
cv2.imshow('result', result)
cv2.waitKey()
как идея использования K-средних
Вы не можете избежать частоты ошибок Байеса.