Подсчет нейронов необходим при исследовании нейродегенеративных заболеваний, для сравнения количества нейронов в срезах здорового и больного мозга, чтобы исследовать влияние различных факторов на развитие патологических процессов.
Зачастую, количество нейронов, окрашенных по методу Ниссля или с помощью иммуногистохимических методов анализируются визуально и подсчитываются вручную с использованием программ PhotoM и ImageJ. Существуют программы автоматического подсчета клеток, но они дают недостоверные результаты при подсчете тел нейронов.
Набор данных, предоставленный лабораторией НИИ РАН, содержит 205 изображений размерами примерно 800 * 600 пикселей, которые представлены в двух видах (нейроны иммунопозитивные к тирозингидроксилазе, где использовался биотин-стрептавидиновый метод иммуногистохимического окрашивания, и нейроны окрашенные по методу Ниссля).
Изображения охватывают достаточное разнообразие возможного количества нейронов на одной картинке (от 50 до 200), однако включают только два вида окрашиваний. Расширение датасета возможно при дополнении его изображениями, содержащими:
- другие типа окрашиваний
- срезы других отделов мозга
image = cv2.copyMakeBorder(src=image, top=2, bottom=2, left=2, right=2, borderType=cv2.BORDER_CONSTANT,value=(255, 255, 255))
image = cv2.copyMakeBorder(src=image, top=2, bottom=2, left=2, right=2, borderType=cv2.BORDER_CONSTANT,value=(255, 255, 255))
Этот шаг необходим для того, чтобы алгоритм видел те нейроны, которые находятся на краю изображения. Без рамки алгоритм не обводит граничные клетки.
Увеличения яркости является необходимым шагом при удалении с изображения шумового окрашивания, которые мешают подсчету количества нейронов на фотографии среза.
kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
image = cv2.filter2D(image, -1, kernel)
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imwrite("gray.jpg", gray_image)
kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
image = cv2.filter2D(image, -1, kernel)
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imwrite("gray.jpg", gray_image)
Матрица kernel является линейной сверткой, которая берет значение яркости окружающих данную точку пиеселей, умножает на -1 и складываем с яркостью центрального пикселя, умноженной на 9. Результат - увеличение резкости изображения. Этот шаг нужен для того, чтобы получить более четкие границы нейронов на черно-белом изображении
ret, threshold_image = cv2.threshold(gray_image_blur, 120, 255, cv2.THRESH_BINARY)
cv2.imwrite("threshold_image.jpg", threshold_image)
ret, threshold_image = cv2.threshold(gray_image_blur, 120, 255, cv2.THRESH_BINARY)
cv2.imwrite("threshold_image.jpg", threshold_image)
Результатом работы этой части кода является бинарное черно-белое изображение. Оно получается из исходного заменой пикселей: если его яркость ниже 120, то он заменяется на 0 (черный), если выше, то на 255 (белый). Пороговое значение подбирается вручную.
threshold_image_blur = cv2.GaussianBlur(threshold_image, (3, 3), 5)
cv2.imwrite("threshold_image_blur.jpg", threshold_image_blur)
threshold_image_blur = cv2.GaussianBlur(threshold_image, (3, 3), 5)
cv2.imwrite("threshold_image_blur.jpg", threshold_image_blur)
ret, threshold_image_new = cv2.threshold(threshold_image_blur, 120, 255, cv2.THRESH_BINARY)
cv2.imwrite("threshold_image_new.jpg", threshold_image_new)
Опыт использования данного кода на нескольких картинках показал необходимость этого шага, которая состоит в очередном шаге удаления аксонов на изображении. Параметры размытия и перевода в бинарное чб подбирались вручную.
edged = cv2.Canny(threshold_image_new, 0, 300, apertureSize = 7, L2gradient=True)
cv2.imwrite("edged.jpg", edged)
edged = cv2.Canny(threshold_image_new, 0, 300, apertureSize = 7, L2gradient=True)
cv2.imwrite("edged.jpg", edged)
nb_blobs, im_with_separated_blobs, stats, _ = cv2.connectedComponentsWithStats(edged)
sizes = stats[:, -1]
sizes = sizes[1:]
nb_blobs -= 1
min_size = 40
max_size = 10000
im_result = np.zeros((edged.shape))
for blob in range(nb_blobs):
if max_size >= sizes[blob] >= min_size:
im_result[im_with_separated_blobs == blob + 1] = 255
cv2.imwrite("final.jpg", im_result)
nb_blobs, im_with_separated_blobs, stats, _ = cv2.connectedComponentsWithStats(edged)
sizes = stats[:, -1]
sizes = sizes[1:]
nb_blobs -= 1
min_size = 40
max_size = 10000
im_result = np.zeros((edged.shape))
for blob in range(nb_blobs):
if max_size >= sizes[blob] >= min_size:
im_result[im_with_separated_blobs == blob + 1] = 255
cv2.imwrite("final.jpg", im_result)
Данный этап необходим для удаления различных артефактов, исходя из их размера. Параметры max_size и min_size, отвечающие соответственно за верхнюю и нижнюю границы условия на размер, были выбраны вручную.
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
closed_final = cv2.morphologyEx(im_result, cv2.MORPH_CLOSE, kernel)
cv2.imwrite("closed_final.jpg", closed_final)
closed_final = closed_final.astype(np.uint8)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
closed_final = cv2.morphologyEx(im_result, cv2.MORPH_CLOSE, kernel)
cv2.imwrite("closed_final.jpg", closed_final)
closed_final = closed_final.astype(np.uint8)
image1 = image.copy()
im_result = im_result.astype(np.uint8)
contours, hierarchy = cv2.findContours(im_result.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(image1, contours, -1, (0,0,0), 2, cv2.LINE_AA)
cv2.imwrite('contours.jpg', image1)
image1 = image.copy()
im_result = im_result.astype(np.uint8)
contours, hierarchy = cv2.findContours(im_result.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(image1, contours, -1, (0,0,0), 2, cv2.LINE_AA)
cv2.imwrite('contours.jpg', image1)
closed_final = closed_final.astype(np.uint8)
contours, hierarchy = cv2.findContours(closed_final.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
new_contours = []
for cnt in contours:
area = cv2.contourArea(cnt)
perimeter = cv2.arcLength(cnt, True)
if area/perimeter > 2.7:
new_contours.append(cnt)
cv2.drawContours(image, new_contours, -1, (0,0,0), 3, cv2.LINE_AA)
cv2.imwrite('contours_new.jpg', image)
closed_final = closed_final.astype(np.uint8)
contours, hierarchy = cv2.findContours(closed_final.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
new_contours = []
for cnt in contours:
area = cv2.contourArea(cnt)
perimeter = cv2.arcLength(cnt, True)
if area/perimeter > 2.7:
new_contours.append(cnt)
cv2.drawContours(image, new_contours, -1, (0,0,0), 3, cv2.LINE_AA)
cv2.imwrite('contours_new.jpg', image)
Этот шаг необходим, чтобы удалить разного рода артефакты, которые не были удалены по условию на размер (на картинке с контурами этими артефактами являются линии). Было предложено условие на отношение площади к периметру (как известно, для линий это отношение близко к 1). Соответственно, если это отношение больше 2.7 (выбрано вручную), то контур удаляется.
coord = []
for c in new_contours:
M = cv2.moments(c)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
cv2.drawContours(image, [c], -1, (0, 0, 0), 2)
cv2.circle(image, (cX, cY), 7, (0, 0, 0), -1)
cv2.putText(image, "center", (cX - 20, cY - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 2)
coord.append((cX,cY))
cv2.imwrite('contours_count.jpg', image)
number_of_neurons = len(coord)
print(number_of_neurons)
coord = []
for c in new_contours:
M = cv2.moments(c)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
cv2.drawContours(image, [c], -1, (0, 0, 0), 2)
cv2.circle(image, (cX, cY), 7, (0, 0, 0), -1)
cv2.putText(image, "center", (cX - 20, cY - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 2)
coord.append((cX,cY))
cv2.imwrite('contours_count.jpg', image)
number_of_neurons = len(coord)
print(number_of_neurons)












