Вы не ищете края (= границы между расширенными областями с высоким и низким значением серого), вы ищете гребни (тонкие линии темнее или ярче, чем их окрестности), поэтому краевые фильтры могут быть не идеальными: краевой фильтр будет дать вам два фланга (по одному на каждой стороне линии) и низкий ответ в середине линии:

ДОБАВИТЬ : Если бы вас попросили объяснить разницу между детектором краев и детектором гребней более четко. Я заранее прошу прощения, если этот ответ становится очень длинным.
Детектор края (обычно) является первым производным оператором: если вы представляете входное изображение в виде трехмерного ландшафта, детектор края измеряет крутизну наклона в каждой точке этого ландшафта:

Если вы хотите определить границы протяженной светлой или темной области, это нормально. Но для вен на изображении ОП вы получите то же самое: контуры слева и справа от каждой вены:

Это также объясняет «двойную линию» в результатах детектора краев Canny:

Итак, как вы обнаруживаете эти тонкие линии (то есть гребни), тогда? Идея состоит в том, что значения пикселей можно (локально) аппроксимировать полиномом 2-го порядка, т.е. если функция изображения равна , то для малых значений и :gxy
g(x,y)≈12x2∂2g∂x2+xy∂2g∂x∂y+12y2∂2g∂y2+x∂g∂x+y∂g∂y+g(0,0)
или в матричной форме:
g(x,y)≈12(xy).⎛⎝⎜∂2g∂x2∂2g∂x∂y∂2g∂x∂y∂2g∂y2⎞⎠⎟.(xy)+(xy).(∂g∂x∂g∂y)+g(0,0)
Производная матрица второго порядка называется " Гессенская матрица ". Он описывает структуру второго порядка, в которой мы заинтересованы.⎛⎝⎜∂2g∂x2∂2g∂x∂y∂2g∂x∂y∂2g∂y2⎞⎠⎟
Часть 2-го порядка этой функции может быть преобразована в сумму двух парабол, повернутых на некоторый угол, путем разложения указанной выше матрицы Гессе на времена вращения диагональной матрицы ее собственных значений ( Матричное разложение ). Нас не волнует вращение (мы хотим обнаружить гребни в любой ориентации), поэтому нас интересуют только иλ1x2+λ2y2λ1λ2
Какие формы могут иметь это приближение функции? На самом деле, не так много

Чтобы обнаружить гребни, мы хотим найти области на изображении, которые выглядят как последние из приведенных выше графиков, поэтому мы ищем области, в которых главное собственное значение гессиана велико (по сравнению с второстепенным собственным значением). Самый простой способ определить это - просто вычислить главное собственное значение для каждого пикселя - и это то, что делает приведенный ниже фильтр гребня.
Хребет фильтр , вероятно , даст лучшие результаты. Я попробовал встроенную в Mathematica RidgeFilter
(которая вычисляет главное собственное значение матрицы Гессена для каждого пикселя) на вашем изображении:

Как видите, для каждой тонкой темной линии есть только один пик. Бинаризация и скелетизация дают:

После обрезки скелета и удаления мелких компонентов (шума) с изображения я получаю этот последний скелет:

Полный код Mathematica:
ridges = RidgeFilter[ColorNegate@src];
skeleton = SkeletonTransform[Binarize[ridges, 0.007]];
DeleteSmallComponents[Pruning[skeleton, 50], 50]
ДОБАВЛЯТЬ:
Я не эксперт по Matlab, я не знаю, имеет ли он встроенный ридж-фильтр, но я могу показать вам, как реализовать его «вручную» (опять же, используя Matematica). Как я уже сказал, ребристый фильтр является основным собственным значением матрицы Гессе. Я могу вычислить это собственное значение в Mathematica символически:
eigenvalue=Last[Eigenvalues[(HxxHxyHxyHyy)]]
=>12(Hxx+Hyy+H2xx+4H2xy−2HxxHyy+H2yy−−−−−−−−−−−−−−−−−−−−−−−√)
Итак, вам нужно вычислить вторые производные , , (используя sobel или производную гауссовского фильтра) и вставить их в выражении выше, и у вас есть свой фильтр гребня. H xy H yyHxxHxyHyy