Разделение объектов на изображении
Я пытаюсь сосчитать объекты на изображении, и написанный мной скрипт MATLAB работает хорошо, но некоторые объекты соприкасаются (на другом изображении они даже перекрываются), и он учитывает только один объект вместо двух. Я пытаюсь использовать bwdist
функция, связанная с функцией водораздела, чтобы отделить эти объекты, но она не работает хорошо. Он режет мои объекты во многих местах, где не должен, и счет намного хуже.
Если бы кто-то мог объяснить мне, как действовать другим, чтобы отделить хорошие частицы (2 касаются справа от моего изображения), я был бы благодарен.
Вот изображение:
Вот мой код (если вы запустите его, появится много цифр):
Извините за написание комментариев на французском =P
clear all;
k=1; % indice pour la numérotation des images
I=imread('Images particules/DSC_0037.jpg');
figure(k)
k=k+1;
imshow(I);
I_hsv=rgb2hsv(I);
figure(k)
k=k+1;
imshow(I_hsv);
I_h=I_hsv(:,:,1);
I_s=I_hsv(:,:,2);
I_v=I_hsv(:,:,3);
figure(k)
k=k+1;
imshow(I_h)
figure(k)
k=k+1;
imshow(I_s)
figure(k)
k=k+1;
imshow(I_v)
%% Hue
[Gx, Gy] = imgradientxy(I_h);
[Gmag, Gdir] = imgradient(Gx, Gy);
% figure(k);
% k=k+1;
% imshowpair(Gmag, Gdir, 'montage');
I_bw1=Gmag>mean(quantile(Gmag,0.99));
figure(k);
k=k+1;
imshowpair(Gmag,I_bw1,'montage');
%% Saturation
[Gx, Gy] = imgradientxy(I_s);
[Gmag, Gdir] = imgradient(Gx, Gy);
% figure(k);
% k=k+1;
% imshowpair(Gmag, Gdir, 'montage');
I_bw2=Gmag>mean(quantile(Gmag,0.99));
figure(k)
k=k+1;
imshowpair(Gmag,I_bw2,'montage');
%% Variance
[Gx, Gy] = imgradientxy(I_v);
[Gmag, Gdir] = imgradient(Gx, Gy);
% figure(k);
% k=k+1;
% imshowpair(Gmag, Gdir, 'montage');
I_bw3=Gmag>mean(quantile(Gmag,0.99)); % choisir le bon quantile
figure(k)
k=k+1;
imshowpair(Gmag,I_bw3,'montage');
%% Addition images du gradient
I_recomp=I_bw1+I_bw2+I_bw3;
figure(k);
k=k+1;
imshow(I_recomp);
%% Dilatation - fill - erosion
% Element structurant diamond
% Dilatation
SE=strel('octagon',3); % doit être un multiple de 3 !
I_dil=imdilate(I_recomp,SE);
% figure(k)
% k=k+1;
% imshow(I_dil);
% Fill
I_fill=imfill(I_dil,'holes');
% Erosion
I_er=imerode(I_fill,SE);
% figure(k)
% k=k+1;
% imshow(I_er);
%% Elimination du bruit en appliquant un imerode <taille minimale des plastiques en pixels
% Erosion - dilatation
SE=strel('octagon',6); % mesurer la taille maximale d'un plastic en pixel avec imdistline !
I_bruit=imdilate(imerode(I_er,SE),SE);
figure(k)
k=k+1;
imshow(I_bruit);
%% Séparation des particules avec watershed
I_bwdist=-bwdist(~I_bruit);
figure(k);
k=k+1;
imshow(I_bwdist,[]);
I_water=watershed(I_bwdist);
I_bruit(I_water==0)=0;
figure(k);
k=k+1;
imshow(I_bruit);
%% Comptage des particules
cc=bwconncomp(I_bruit);
cc.NumObjects
L=labelmatrix(cc);
RGB_label=label2rgb(L);
figure(k);
k=k+1;
imshow(RGB_label);
2 ответа
Это нетривиальная проблема, и есть много возможных решений. Примечание: я не использовал imgradientxy
а также imgradient
как в вашем коде, потому что моя версия matlab слишком старая (R2011b).
На мой взгляд, нужно сделать как минимум две вещи:
отделение фона от других объектов. В этом случае я использую эту реализацию определения порога Otsu (выполняется на изображении, преобразованном в каналах HSV с пороговым уровнем четырех классов):
http://www.mathworks.com/matlabcentral/fileexchange/26532-image-segmentation-using-otsu-thresholding
Может быть полезен другой цвет фона.
анализировать каждый объект в одиночку, чтобы избежать помех. Можно выполнить другой тип анализа:
математические морфологические операции:
imerode
/imdilate
сегментация текстуры (фильтр Габора, энтропия, однородность или уровень интенсивности). Вот несколько примеров:
http://it.mathworks.com/help/images/image-segmentation-1.html
Также ознакомьтесь с руководством по сегментации с помощью фильтра Габора и страниц с примерами и концепциями.
Лично я сначала выполняю этап imerode
/imdilate
операции по нарезке начальных объектов.
Я думаю, что лучше стирать подобъекты слишком маленькие:
- с дополнительным эрозионным проходом, используя небольшой элемент struct
bwareaopen
с небольшим процентом от общей площади исходного объекта.
Если какие-либо подобъекты все еще существуют, должна быть возможность лучше отделить подобъекты с помощью одного из методов, перечисленных выше. Я использую математическую морфологию (эрозия немного медленная), снова сегментацию Оцу, разницу в изображении и в конечном итоге bwselect
контролировать результат.
Вот мой код и вывод.
clear all;
close all;
clc;
n_level = 4;
channel_n = 2;
R1_perc = 1 / 8;
R2_perc = 5 / 100;
area2_perc = 2 / 100;
R3_small = 1;
I = imread('~/Downloads/25443957.jpg');
I_hsv = rgb2hsv(I);
% % indentify image background as the object with greatest area
% original Otsu implementation
Ihsv_otsu = otsu(I_hsv, n_level);
areas_extension = zeros(1, n_level);
for x = 1:n_level
img_area_object = Ihsv_otsu == x; % img_zona = (Ihsv_otsu_bwl==n);
areas_extension(1, x) = bwarea(img_area_object);
end
[background_val, background_idx] = max(areas_extension);
Ihsv_otsu_filled = imfill(Ihsv_otsu ~= background_idx, 'holes');
% % divide et impera
% analyze the objects one by one
Ihsv_otsu_bwl = bwlabel(Ihsv_otsu_filled);
img_morph = zeros(size(Ihsv_otsu_bwl));
img_eroded = zeros(size(Ihsv_otsu_bwl));
SE1px1 = strel('disk', 1);
SE3small = strel('disk', R3_small);
fprintf('start loop MORPH, Operations:: erosion & dilate...\n')
for n = 1:max(unique(Ihsv_otsu_bwl))
fprintf('morph loop... iter:%d\n', n)
img_zona = Ihsv_otsu_bwl == n;
temp = regionprops(img_zona, 'MinorAxisLength');
minoraxis = temp.MinorAxisLength;
img_zona_out = img_zona;
R = ceil(minoraxis * R1_perc); % use 'ceil', not 'round', to avoid R<1 processing small regions
SE2R1 = strel('disk', R);
CC = bwconncomp(img_zona_out, 8);
k = 0;
while CC.NumObjects == 1
img_zona_out = imerode(img_zona_out, SE2R1);
CC = bwconncomp(img_zona_out, 8);
k = k + 1;
% %% it is possible to erode with size~1-pixel object or not
% if CC.NumObjects>1
% fprintf('FOUND! iter:%d, c_EROSION:%d, n_Obj:%d\n', n, k, CC.NumObjects)
% elseif CC.NumObjects==0
% fprintf('BREAK! iter:%d, c_EROSION:%d, n_Obj:%d\n', n, k, CC.NumObjects)
% break
% %elseif CC.NumObjects==1
% % fprintf('keep carry on... iter:%d, c_EROSION:%d, n_Obj:%d\n', n, k, CC.NumObjects)
% end
if CC.NumObjects > 1
img_zona_out = imerode(img_zona_out, SE3small);
CC = bwconncomp(img_zona_out, 8);
if CC.NumObjects > 1
fprintf('FOUND! iter:%d, c_EROSION:%d, n_Obj:%d\n', n, k, CC.NumObjects)
elseif CC.NumObjects == 0
% fprintf('BREAK! iter:%d, c_EROSION:%d, n_Obj:%d\n', n, k, CC.NumObjects)
break
end
end
end
% %% post-erosion:
% if the object number is 0, drop the eroded image and mantain the pre-eroded image
if CC.NumObjects == 0
img_morph = imadd(img_morph > 0, img_zona);
img_eroded = imadd(img_eroded > 0, img_zona);
continue
% if the object number is greater than 1, dilate the objects until
% they touch, when the CC.NumObjects is 1
elseif CC.NumObjects > 1
k = 0;
img_zona_dilate = img_zona_out;
while CC.NumObjects > 1
k = k + 1;
img_zona_old = img_zona_dilate;
% a small radius is better for a uniform expansion
img_zona_dilate = imdilate(img_zona_dilate, SE3small);
CC = bwconncomp(img_zona_dilate > 0, 8);
if CC.NumObjects == 1
% %% results the last objects immediatly before they touch
img_eroded = imadd(img_eroded > 0, img_zona_old);
fprintf('UNITED! iter:%d, c_DILATE:%d, n_n_Obj:%d\n\n', n, k, CC.NumObjects)
end
end
% modified Otsu function (otsuSeparation.m)
img_splitted = otsuSeparation(I_hsv, img_zona, channel_n, R2_perc, area2_perc);
img_morph = imadd(img_morph > 0, img_splitted > 0);
elseif CC.NumObjects == 1
fprintf('# only one object... strange at this point.\n#')
fprintf('# iter:%d, c_DILATE:%d, CC.NumObjects:%g\n', n, k, CC.NumObjects)
end
end
% %
fprintf('start loop BWSELECT:: img_morph & img_eroded...\n')
img_eroded_bwl = bwlabel(img_eroded);
img_result = zeros(size(img_eroded_bwl));
for X = 1:max(unique(img_eroded_bwl))
fprintf('# BWSELECT, iter:%d\n', X)
obj2select = img_eroded_bwl == X;
centr = regionprops(obj2select, 'centroid');
xc = round(centr.Centroid(1));
yc = round(centr.Centroid(2));
temp = bwselect(img_morph, xc, yc);
img_result = imadd(img_result > 0, temp);
end
img_result = img_result > 0;
%%
close all
figure('Name', 'morph', 'NumberTitle', 'off', 'WindowStyle', 'docked');
imagesc(img_morph)
figure('Name', 'erodeed', 'NumberTitle', 'off', 'WindowStyle', 'docked');
imagesc(img_eroded)
figure('Name', 'hsv', 'NumberTitle', 'off', 'WindowStyle', 'docked');
imagesc(I_hsv)
figure('Name', 'image', 'NumberTitle', 'off', 'WindowStyle', 'docked');
imagesc(I)
figure('Name', 'result', 'NumberTitle', 'off', 'WindowStyle', 'docked');
imagesc(img_result)
Вот моя модификация функции Оцу, OtsuSeparation.m
,
function [img_splitted] = otsuSeparation(I, bw, channel_n, R2_perc, area2_perc)
img_splitted = zeros(size(bw));
SE1px1 = strel('disk', 1);
% fprintf('start loop: Otsu segmentation...\n')
% for Y=1:max(unique(img_bwl)) %for Y=41:41
% fprintf('# OtsuSep: iter:%d\n', Y)
% bw = img_bwl==Y;
img_channel = I(:, :, channel_n);
img_channel(bw == 0) = 0;
% image segmentation by intensity
img_channel_otsu = otsu(img_channel);
% processing "BW" to avoid multiple objects and use only one valor for MinorAxisLength
temp2 = regionprops(bw, 'MinorAxisLength', 'Area');
R2 = ceil(temp2.MinorAxisLength * R2_perc);
area2 = ceil(temp2.Area * area2_perc);
SE3R2 = strel('disk', R2);
% %% PART1: cleaning principal object selected by Otsu segmentation
% removing small objects external to main selection with bwareaopen to
% avoid modification at object contours
img_part1 = bwareaopen(img_channel_otsu > 1, area2, 8);
img_part1 = imfill(img_part1, 'holes');
% execting imdilate to avoid intersection between PART1 and PART2 complements
img_part1 = imdilate(img_part1, SE3R2);
img_complement1 = imcomplement(img_part1);
% %% PART2: cleaning external area around PART1 object
% execting imerode to avoid intersection between PART1 and PART2 complements
img_bw_eroded = imerode(bw, SE3R2);
img_complement2 = imcomplement(img_bw_eroded);
img_part2 = img_complement1 - img_complement2;
img_part2 = bwareaopen(img_part2 > 0, area2, 8);
% dilate (after erosion) to restore original object size and morphology
img_part2 = imdilate(img_part2, SE3R2);
% securing a well-done separation between objects
img_intersez = imadd(img_part1, img_part2) > 1;
img_intersez = imdilate(img_intersez, SE1px1);
% use manual substraction here instead of imabsdiff for a good separation
img_part1 = (img_part1 - img_intersez) > 0;
img_part2 = (img_part2 - img_intersez) > 0;
img_splitted = imadd(img_splitted, imadd(img_part1, img_part2));
Конечно, можно сделать разные выборы. Чтобы повысить точность, может потребоваться попробовать алгоритм с несколькими дополнительными образцами изображений и проверить его с помощью контрольной группы различных изображений.
Здесь мой вывод.
Оттенки серого (цветная версия):
Оттенки серого (изображение в формате Matlab):
ОБНОВЛЕНИЕ: мой код нуждается в улучшении. Во время фазы "ИДИЛАТ ПО ИМЕРОДЕ" я предполагаю, что бинарный объект должен быть разделен на две части. Для улучшения алгоритма можно использовать также обнаружение краев:
http://robotics.eecs.berkeley.edu/~sastry/ee20/index.html
http://it.mathworks.com/help/coder/examples/edge-detection-on-images.html
http://www.mathworks.com/matlabcentral/fileexchange/45459-canny-edge-detection-in-2-d-and-3-d
Другие общие ресурсы о сегментации изображения:
https://en.wikipedia.org/wiki/Outline_of_object_recognition
http://it.mathworks.com/help/images/object-analysis.html
http://it.mathworks.com/help/images/texture-analysis-1.html
http://it.mathworks.com/help/images/image-segmentation-1.html
Сегментация - это всегда сложная проблема. В общем, довольно сложно ответить, каков наилучший способ решения вашей проблемы, я думаю, что на каждом изображении перекрывающиеся частицы будут выглядеть по-разному. Вы можете использовать форму, цвет, текстуру, предварительные знания (обучение) и т. Д. Ваши данные, вероятно, будут определять, что будет работать лучше всего
Я просто опишу здесь простой метод разделения соприкасающихся частиц, основанный на форме частиц и известный как разделение на водоразделе. Процесс начинается с двоичного изображения (которое вы уже получили, установив порог). Затем вы бы вычислили карту расстояний ( bwdist). Тогда одним из вариантов является постепенное расширение конечных размытых точек до тех пор, пока они не встретятся с черным пикселем, который будет использоваться для формирования вашего сегмента разделения.