皮肤癌检测这五个字,在医学图像处理圈子里算是经典的入门级课题,但也正因为入门门槛看起来不高,“做得像样”和“做成系统”之间的差距反而很明显。我这两年陆陆续续带过几个基于MATLAB的皮肤影像辅助诊断原型项目,基本都绕不开同一条技术路线:用图像处理把临床上判断黑色素瘤的ABCDE经验规则量化成特征,再通过GUI把这些算法组织成完整可交互的界面。这套系统解决的现实问题,是黑色素瘤早期和良性色素痣外观相似、靠肉眼主观判断差异大,基层医疗和患者自检场景里尤其需要一个可量化、可复现、带视觉反馈的辅助筛查工具。适合参考这套思路的,主要是医工交叉方向的学生、对医学图像分析感兴趣的MATLAB开发者,以及想做计算机辅助诊断预研的技术人员。
1. 项目定位与整体思路拆解
1.1 为什么这个场景适合用MATLAB来做
很多人一上来就问,为什么偏偏用MATLAB搞医学图像?用Python不香吗?我的看法是,如果你做的是产品落地,那Python的生态确实更占优势;但如果处在算法验证和课题研究阶段,MATLAB有它独特的优势。首先是图像处理工具箱非常成熟,灰度化、滤波、形态学、连通域分析、特征提取这些操作,基本是函数一调就完事,不需要自己从零写底层逻辑。其次是它的矩阵化思维处理图像非常自然,图像本身就是一个二维矩阵,很多算法步骤用几行向量化代码就能写清楚,调试时随时能停在任何一步看中间结果,这对医学影像这类需要反复确认区域细节的场景特别友好。
第三点是GUI快速原型能力,不管是用老的GUIDE还是新的App Designer,拖几个按钮、坐标轴、表格控件,再把回调函数接上,一个能演示、能交互的原型界面半小时就能拼出来。这比用通用桌面框架搭界面要省事得多。当然MATLAB的短板也很明显:运行时体积大、程序部署要装运行时环境、商业授权价格不低。但这些在课题演示、科研验证、教学实验的场景里都不是致命问题。只要明确它的定位是“辅助算法研究”而不是“最终产品”,工具选型的取舍就清晰了。
1.2 系统要解决的核心问题与整体五步流程
皮肤癌检测,准确说是一个辅助筛查问题,不是确诊问题。临床上医生看一个色素性皮损,会遵循一套叫ABCDE的经验法则:A看不对称性(Asymmetry),B看边缘(Border),C看颜色(Color),D看直径(Diameter),E看进展(Evolution)。恶性黑色素瘤往往在不对称、边缘不规则、颜色混杂、直径偏大、近期有变化这些维度上表现异常。问题是这些标准都很主观,边缘不规则到什么程度算不规则,颜色混杂到什么程度算混杂,不同医生的判断差异非常大。
这套项目的本质,就是把ABCDE这组模糊描述转化为可计算的图像特征。对称性可以通过病变区域轴翻转后的重叠面积来量化,边缘可以通过轮廓的紧凑度或凸包比例来量化,颜色可以通过RGB通道的统计分布来量化,直径可以通过像素当量换算估算。最终系统输出的不是“我觉得像恶性”这种主观结论,而是一组量化特征值加一个风险等级,医生看到的是可解释的辅助结果。
从系统架构上看,固定流程是五步:图像导入、预处理、病变分割、特征提取、分类判定。GUI在中间负责的是流程编排和结果可视化的角色。用户点按钮,系统依次在后台调用对应的算法模块,然后把原始图、中间图、特征表和判定结论都展示在界面上。这个结构非常清晰,也方便后续把某个模块替换成更高级算法,比如把传统阈值分割换成深度学习分割网络,GUI框架本身不需要大改。
2. 预处理与病变分割:决定成败的前两关
2.1 预处理:噪声、光照、毛发三大干扰的处理顺序
实际做的时候,很多人拿到图片第一件事就是咪转灰度然后找阈值,结果分割出来的区域乱七八糟,不是把毛发当成病变,就是把角落阴影当成一大片皮肤。问题不出在分割算法,而出在预处理没做够。皮肤镜或者手机微距拍出来的皮肤图像,常见干扰有三类:图像噪声、光照不均、毛发遮挡。噪声主要影响边缘检测和阈值稳定性;光照不均会使灰度分布偏移,同一张图里有的区域皮肤亮度极高、有的区域发暗;毛发则会被误分割成细小暗色区域,在后续面积和边界特征计算中引入很大偏差。
预处理环节我常用的顺序是:先灰度化,然后中值滤波去噪,再做对比度受限自适应直方图均衡化(CLAHE),最后视情况做光照校正。灰度化直接用rgb2gray,这是几乎所有后续算法的基础。中值滤波比高斯滤波更适合这个场景,因为皮肤镜图像里的噪声有不少是椒盐性质的稀疏亮暗点,中值滤波对这类噪声有天然的克制效果,同时能相对好地保留边缘位置。窗口大小一般取3×3或5×5,我实测3×3对边缘细节保留更好,5×5去噪更干净,如果图像分辨率不高,优先用3×3。
CLAHE这一步很多人会忽略,但对后续分割帮助特别大。你可以把它理解成把图像切成一个个小区域,在每个小区域里分别做亮度均衡,同时限制增强幅度,避免把噪声一起放大。调用方式如下:
% 灰度化与预处理 imgGray = rgb2gray(rgbImg); imgDenoised = medfilt2(imgGray, [3 3]); imgEnhanced = adapthisteq(imgDenoised, 'ClipLimit', 0.03, 'NumTiles', [8 8]);ClipLimit是限制对比度增强幅度的参数,太小了增强效果不明显,太大了局部噪声被明显放大,我一般从0.02开始试,稳定在0.03左右。NumTiles决定分块粒度,8×8对大多数皮肤镜照片都够用,图像特别大时可以适当提高。
2.2 病变分割:从阈值初筛到形态学精修
分割是整套系统承上启下的环节,分不好,后面特征提取全是白算。最简单的做法是Otsu全局阈值,MATLAB里一句话就能拿到阈值:
threshold = graythresh(imgEnhanced); mask = imbinarize(imgEnhanced, threshold);但这有一个特别常见的坑:Otsu找到的是让前景背景方差最大的阈值,它并不知道前景到底是病变还是皮肤。有的图像浅色皮肤占比大、病变偏暗,分割后病变呈现为白色前景;有的图像背景暗、病变反而偏亮,结果又会反过来。所以拿到mask之后要看一下前景面积占比,如果超过50%,大概率是反了,需要取反。我习惯加一句条件判断:
if sum(mask(:)) / numel(mask) > 0.5 mask = ~mask; end另外,很多皮肤镜照片四周有一圈黑色背景,灰度值很低,二值化后会把整个图像边框圈成一个连通区域,不处理掉会严重影响病变区域的真实比例。我的处理方式是:删除所有与图像边界相连的前景连通域,再提取剩余区域中最大的连通域作为候选病变区域。这一步可以串联多个形态学函数实现:
maskCleaned = imclearborder(mask, 4); maskCleaned = bwareaopen(maskCleaned, 50); maskCleaned = imclose(maskCleaned, strel('disk', 5)); maskCleaned = imfill(maskCleaned, 'holes');这四个操作各有分工:imclearborder去掉图像四周的边框;bwareaopen删除像素数小于50的小碎片;imclose用半径为5的圆盘结构元素闭合缺口;imfill把区域内部的孔洞填上。为什么用imclose而不是先用imopen?因为皮肤病变区域内部经常有颜色很浅的斑点,这些斑点在阈值分割时会漏掉,形成孔洞。如果不先闭合,后续面积、周长都会偏小,周长尤其会被孔洞毛边污染。先闭合、再填洞,才能得到一个相对规整的连通区域。
结构元素半径要注意控制,3到10像素是常用范围。核太大容易把真实边界也磨掉,我习惯先用5,效果不满意再往回调。如果你手里的图是普通手机拍的,分割前还有一个小技巧:用HSV颜色空间的S通道或V通道作为分割输入,有时比直接灰度化更好,因为皮肤偏红、病变偏棕黑,饱和度和亮度通道上的区分度往往更高。转HSV后用要用的通道,再走同样的二值化和形态学链路。
2.3 分割结果的可视化验证习惯
分割完成之后不要急着进特征提取,先把分割结果叠加到原图上检查一遍。这一步看起来只是调试,实际能拦截掉大部分特征计算错误的根源。常用做法是把mask叠加到原图上,用伪彩色标出病变区域:
% 叠加显示分割结果 overlay = imoverlay(rgbImg, maskCleaned, [1 0 0]); figure; imshow(overlay);我在调试阶段会把预处理后的增强图、二值初筛mask、形态学处理后的mask、原图叠加结果,用subplot并列在一个figure里。这样每个环节哪里出了问题一眼就能定位:增强图太黑,多半是ClipLimit或光照校正没到位;初筛mask一片噪声,是滤波窗口太小;最终mask缺一块,那多半是imclose或imfill参数需要调。这个可视化验证的思路,比盯着命令行输出数字要直观得多。搭建GUI时也建议保留一个“中间结果预览”功能,不要只显示最终结果。
3. 特征提取:把ABCDE经验规则变成程序能算的数字
3.1 形态特征:对称性与边缘不规则度的计算思路
分割得到的是病变区域的二值mask,从这一刻起,所有基于mask的特征计算都是在跟几何问题打交道,不再直接处理原图像素。
对称性是第一个特征。临床上的“不对称”不是数学意义上的绝对对称,而是说病变区域两侧形态差异较大。程序里比较常用的做法是:先用regionprops求mask的质心,然后分别沿垂直轴和水平轴做镜像翻转,计算翻转后的mask与原始mask的重叠面积比例。重叠比例越高说明越对称。大致思路如下:
% 以质心为轴计算左右对称性 regions = regionprops(maskFinal, 'Centroid'); cx = round(regions.Centroid(1)); leftPart = maskFinal(:, 1:cx); rightPart = maskFinal(:, cx+1:end); % 将右侧左右翻转后缩放对齐,计算重叠面积 rightFlipped = fliplr(rightPart); % 交集 / 并集 得到对称重叠率实际编码时要注意两侧宽度不一致的问题,需要做尺寸对齐,但核心思想就是“形状翻转后能重合多少”。这个思路和把左右脸照片叠起来看相似度是同一个道理。
边缘不规则度是第二个关键特征。良性色素痣往往边缘光滑,恶性病变边缘常呈锯齿状或伪足状。这里有现成的量化指标:紧凑度。定义是周长的平方除以4π乘面积,公式是 compactness = Perimeter² / (4π × Area)。一个标准圆形的紧凑度接近1,边缘越不规则、形状越碎,数值越大。用MATLAB的regionprops一次能拿到Area和Perimeter:
stats = regionprops(maskFinal, 'Area', 'Perimeter', 'ConvexArea'); area = stats.Area; perimeter = stats.Perimeter; compactness = perimeter^2 / (4 * pi * area); % 凸包占比:区域面积 / 凸包面积 convexRatio = stats.Area / stats.ConvexArea;凸包占比也是一个很好的补充指标。凸包可以理解成把一条橡皮筋绷在整个区域外缘形成的形状,如果病变边缘有很多锯齿,凸包面积会明显大于实际面积,这个比值会明显小于1,边缘越规整越接近1。紧凑度和凸包占比一起用,对边缘不规则度的描述比单一指标稳健得多。
3.2 颜色特征与纹理特征:灰度图看不到的信息
形状特征之后是颜色。黑色素瘤的一个典型特征是颜色混杂,同一个病灶里可能出现深浅不一的棕色、黑色,甚至红色、白色、蓝色区域。单纯看灰度图会把颜色差异全部抹平,所以颜色特征必须在RGB或者HSV空间里算。基础做法是统计病变区域内RGB三个通道的均值、标准差、不同颜色通道的相关系数。均值描述整体色调,标准差描述颜色分散程度,黑色素瘤的颜色标准差往往比良性痣大。
更细致一点,可以对病变区域的像素颜色做K-means聚类。聚类中心的数量就是主要颜色的数量,正常良性痣往往两三个主色就够,而颜色混杂的恶性病变可能需要四五个主色才能描述。这个特征比较实用,我把它作为颜色复杂度的核心指标之一。代码很简单:
% 提取病变区域像素的RGB值后聚类 pixels = double(reshape(rgbImg(repmat(maskFinal, [1 1 3])), [], 3)); [idx, centers] = kmeans(pixels, 4, 'Replicates', 3); % 统计每个聚类的像素占比,作为颜色分布特征纹理特征是对颜色特征的重要补充。有些病变颜色分布看着均匀,但内部纹理颗粒感、粗糙度差异很大,这时候灰度共生矩阵(GLCM)就派上用场。MATLAB的graycomatrix和graycoprops是现成工具:
glcm = graycomatrix(imgGrayOnLesion, 'Offset', [0 1; -1 1; -1 0; -1 -1], 'NumLevels', 32); textureStats = graycoprops(glcm, {'contrast', 'energy', 'homogeneity', 'correlation'});这里的四个统计量各有含义:对比度度量像素对之间灰度差的大小,差越大纹理越粗糙;能量度量纹理的规则程度,纹理越均匀能量越高;同质性度量局部灰度均匀性;相关性反映纹理在某个方向上的延伸规律。Offset指定计算方向,[0 1]是水平方向,[-1 1]是45度方向,[-1 0]是垂直方向,[-1 -1]是135度方向。四个方向都算一遍再取均值,会更全面。NumLevels一般取32或64,太小会丢失灰度细节,太大计算量大且对噪声敏感。
3.3 直径特征与像素毫米换算
直径特征是ABCDE法则里的D,临床上常用“直径大于6毫米需要警惕”作为参考线。但图像处理里的直径不是直接在图上量一段像素距离就完事,因为不同设备、不同拍摄距离下,同样大小的病变在图像上的像素尺寸差异巨大,必须把像素换算成物理尺寸。
最正规的换算方式是在拍摄时放一把刻度尺或贴一个已知直径的圆形标定纸片,程序里检测这个标定物的像素直径,算出“每毫米对应多少像素”的比例尺。没有标定物时,也可以用皮肤镜自带的DPI信息换算:物理尺寸(毫米)= 像素数 × 25.4 / DPI。如果两张信息都没有,那就只能输出相对直径或者像素直径,同时明确提示用户这个值没有经过物理标定,只能做同一设备下的相对比较。
直径的具体算法上,regionprops里的MajorAxisLength可以当作近似的Féret直径,它表示区域外接椭圆的长轴长度,对应病变最长的方向跨度。在测量时我会取平均直径而不是单一方向直径,更稳定一些:
stats = regionprops(maskFinal, 'MajorAxisLength', 'MinorAxisLength', 'Area'); % 等效直径 = 和区域面积相等的圆形的直径 equivDiameter = 2 * sqrt(stats.Area / pi); % 长轴方向和短轴方向分别作为最大和最小尺寸参考注意一个问题:医学上说的直径其实指的是临床医生从皮损最宽处量的长度,跟图像里因拍摄角度产生的投影尺寸之间会有偏差。拍摄时镜头必须尽量正对皮损,否则斜向拍摄会把实际尺寸放大或缩小。这一点虽然不直接影响代码,但在系统说明书里一定要提醒使用方。
4. GUI集成与分类判定:从算法到完整系统
4.1 界面布局:怎么安排控件才顺手
算法链路跑通之后,下一步是把所有东西封装进一个GUI。界面布局我总结出一个比较顺手的习惯:左侧放图像显示区,用于展示原图、预处理结果、分割结果和特征图,通过一个下拉框或按钮组切换当前显示内容;右侧放操作按钮,从上到下按流程顺序排列,依次是打开图像、预处理、病变分割、特征提取、分类判定;下方放特征表格和结果提示文本区。
图像显示区我用三个axes控件,第一个显示原图,第二个显示当前处理中间结果,第三个显示分割叠加结果。整个流程跑完后,中间结果显示增强图,第三个显示分割覆盖层,用户一眼能看出处理前和处理后的差异。特征表格用uitable控件,分类结论用一段加粗文本,比如“风险等级:中高风险,请结合临床进一步检查”。按钮排列顺序和用户操作逻辑保持一致,不要打乱,这是GUI交互设计里最容易犯的错误——功能全但顺序乱,用户根本不知道怎么操作。
在GUI工具上,新版MATLAB推荐App Designer,传统教学项目里很多还用GUIDE。我的建议是:如果你只是快速做个原型且熟悉回调函数,GUIDE够用;如果要从头开始学,直接学App Designer,界面更现代,后续维护也更方便。无论是哪一种,底层的算法函数都可以共用,GUI本身不关心这些算法是用什么工具创建的。
4.2 回调函数与模块化封装
我见过不少失败的实现,把几百行图像处理代码直接堆在按钮回调里,结果界面一卡一卡的,出错还很难定位。正确的做法是,把算法封装成独立函数,GUI回调里只做调用和显示。我常用的模块划分是这样的:
- preprocessImage.m:输入RGB图,输出增强后的灰度图和处理参数
- segmentLesion.m:输入增强灰度图,输出病变二值mask
- extractFeatures.m:输入原图和mask,输出特征结构体
- classifyLesion.m:输入特征向量,输出风险等级和概率
这样每个函数单独测试,GUI回调里只需要几行代码完成调用和界面更新。举个打开图像回调的例子:
function btnOpen_Callback(hObject, eventdata, handles) [filename, folder] = uigetfile({'*.jpg;*.png;*.bmp', '图像文件'}); if isequal(filename, 0) return; end handles.rgbImg = imread(fullfile(folder, filename)); axes(handles.axesOriginal); imshow(handles.rgbImg); guidata(hObject, handles); end数据在回调之间传递最常用的机制是handles结构体。打开的图像、预处理参数、分割mask、特征结果都存进handles,每修改一次就调用guidata更新,下一个回调里用handles.xxx取出来。这里有个容易踩的坑:MATLAB对象句柄在回调之间默认是快照式的,如果你在一个回调里直接改handles的字段但不调用guidata,其他回调里根本读不到改动后的值。每次更新数据之后立刻guidata是习惯问题,养成就好。
特征提取的结果可以用结构体组织,比如features.symmetry、features.compactness、features.meanColor等,再把结构体字段和数值填进uitable。uitable显示特征表的时候,如果是新版Timetable或者普通cell数组都可以,建议用cell数组先拼好:
data = {'对称性', features.symmetry; '紧凑度', features.compactness; '颜色标准差', features.colorStd}; set(handles.uitableFeatures, 'Data', data);4.3 分类器选择与性能评估
特征提取完之后进入分类环节。最简单的方案是规则评分,也就是给ABCD特征各一个权重,按总分划分风险等级。这个方案完全可解释,逻辑透明,适合做教学演示,比如总分高于一个阈值判为高风险,低于另一个阈值判为低风险。规则评分的缺点是权重靠人工试出来,对不同数据集的适应力弱。
更强一点的方案是用机器学习分类器。MATLAB的Statistics and Machine Learning Toolbox里有fitcsvm、fitcknn、fitctree等函数可以直接调用。我的经验是,在特征量不大的情况下,SVM和KNN的稳定性都不错。SVM擅长在高维空间里找分类边界,但对特征尺度敏感,所以要先做标准化:
mdl = fitcsvm(featureMatrix, labels, 'Standardize', true, 'KernelFunction', 'rbf'); cvmdl = crossval(mdl, 'KFold', 5); loss = kfoldLoss(cvmdl); accuracy = 1 - loss;交叉验证这一步别省。用同一批数据既训练又评估,准确率虚高得厉害,交叉验证才能反映模型对新数据的真实泛化能力。不同分类器之间的对比可以参考下表:
| 分类器 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 规则评分 | 可解释性强、无需训练 | 权重难调、泛化弱 | 教学演示、小样本 |
| SVM | 高维分类能力强、稳定 | 参数敏感、黑盒 | 特征数量较多的科研验证 |
| KNN | 实现简单、无需训练过程 | 样本量大时预测慢 | 中小规模数据集 |
| 决策树 | 可视化规则、可直接解释 | 容易过拟合 | 需要规则解释的场景 |
评估分类器不能只看准确率,尤其医学场景里样本经常不平衡,良性样本远多于恶性样本。这时候要同时看灵敏度(恶性样本里被正确检出的比例)和特异度(良性样本里被正确判为良性的比例)。对于筛查系统,灵敏度的优先级高于特异度,漏检的成本远高于误报。confusionmat函数可以几行算出混淆矩阵,再从中提取这些指标。
confMat = confusionmat(testLabels, predictedLabels); TP = confMat(2,2); FN = confMat(2,1); FP = confMat(1,2); TN = confMat(1,1); sensitivity = TP / (TP + FN); specificity = TN / (TN + FP);5. 实测效果、常见问题与改进方向
5.1 我在公开皮肤镜数据集上的实测感受
我用这套流程在一个公开皮肤镜图像数据集子集上做过测试,图像来源比较杂,有的带标尺,有的不带,拍摄条件差异不小。整体感受是:在画质较好、病变居中的标准皮肤镜图上,分割成功率高,特征提取稳定,SVM分类准确率能做到85%左右;但一旦换成普通手机在自然光下拍的照片,分割效果就明显下降,分类准确率掉到75%甚至更低。
最影响效果的因素排名第一是毛发遮挡。黑色毛发在灰度图上和黑色素瘤病变本身的灰度接近,阈值分割时要么把大片毛发当病变,要么因为毛发干扰把病变边界吃得支离破碎。排名第二是光照不均匀,尤其在皮肤镜没有偏光功能的情况下,图像中央亮、四周暗,阈值分割很容易把暗角误判为病变区域。排名第三是病变区域太小,比如只有几个毫米直径的早期小痣,在整幅图里只占很小比例,预处理增强后细节容易丢失。
我自己的体会是,这个项目真正值钱的部分不是最后那个分类准确率数字,而是每一步中间结果是否稳定、可复现。算法流程固定下来以后,换一批图跑,预处理参数基本不动,分割能稳定给出合理区域,特征计算能保持一致,这个系统的可用性就比堆一堆花哨但不可复现的算法强得多。
5.2 常见问题排查速查表
把实操中遇到最多的问题整理成一张速查表,排查思路按这个表来能省很多时间。
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 分割结果把整个图片边框都算进去了 | 背景暗部被Otsu判为前景 | 用imclearborder清除与边界连通区域 |
| 分割出的病变区域偏大,包含一圈皮肤 | CLAHE增强后对比度过高 | 降低ClipLimit,或改用HSV的S通道分割 |
| 病变区域内部大量空洞 | 病变内含浅色斑块,阈值未覆盖 | 先用imclose闭合,再imfill填洞 |
| 毛发被分割成细长暗色区域 | 预处理未过滤毛发 | 增加形态学开运算去除细线,或先做毛发检测修复 |
| 同一张图多次运行结果不同 | 存在随机初始化算法(如K-means) | 固定随机种子,或增加Replicates重复次数 |
| GUI点击按钮无反应 | 回调函数中报错被隐藏 | 在回调开头加try-catch,用disp输出错误信息 |
| 报错“未定义函数或变量” | 算法函数不在当前工作路径 | addpath添加函数所在目录,或统一放进当前工程目录 |
| 特征数值变化特别大 | 图像尺寸差异导致像素统计不稳 | 预处理阶段统一缩放图像到固定长边,如512或1024 |
| 不同照片亮度差异大影响颜色特征 | 缺少色彩校准 | 用白平衡卡片校准,或提取颜色比例特征而非绝对亮度 |
5.3 可扩展的改进方向
如果你不想止步于传统图像处理的方案,这个系统的框架可以平滑升级。分割环节可以把二值阈值替换成深度学习分割网络,比如U-Net或DeepLab系列,输出仍然是二值mask,因此GUI和特征提取完全不用改,只换segmentLesion函数内部实现。分类环节也可以用迁移学习的方式,把最后一个全连接层拿掉,将预训练网络提取的深度特征拼进现有特征向量,然后继续用SVM分类,我试过这种方式,深度学习特征和传统手工特征合并后,对普通照片的鲁棒性提升会比较明显。
另外可以加一个导出报告模块,把原始图、分割结果、特征表和分类结论合成一个PDF或Excel文件,方便临床随访留存。这个功能用MATLAB自带的报表生成工具或者直接操作Excel接口都能做,集成到GUI里也就一个按钮的事。如果条件允许,加年龄、性别、发病部位这些临床元数据进分类器,也会对最终判断有正向影响,因为黑色素瘤在某些部位和特定人群中的发病率差异很大。
还有一点容易被忽略:界面里加一个“免责声明”,标注本系统为辅助筛查工具,不能替代专业医生诊断。这不是形式主义,而是计算机辅助诊断工具必须有的边界意识。
我个人在实际使用中最大的体会是,这类系统最忌讳的就是拿着原始图直接跑分割。毛发、反光、暗角、标尺边缘,这些干扰项不提前处理,后面所有特征计算都会建立在错误区域上。把预处理和分割做扎实,比在分类器上花再多心思都重要。最后再分享一个习惯:开发这个系统时一定要保留可视化调试界面,每一步中间结果都肉眼验证过,特征算出来的数字才敢信。这个原则不只适用于皮肤癌检测,几乎所有医学图像处理项目都适用。