MATLAB接触角程序改造指南:从能跑到算得准的关键细节
2026/8/30 4:04:51 网站建设 项目流程

简介:本资源是一套面向材料科学、表面物理及实验数据分析领域的MATLAB工具包,聚焦接触角测量与固体表面能反演这一核心问题,适用于高校研究生、科研人员及工程技术人员开展润湿性表征与界面性质研究。压缩包共含10个.m文件,总大小仅6KB,以主程序Contactangle.m为核心,辅以多个fpush/fpop系列函数,构成完整的图像边缘提取、液滴轮廓拟合与接触角计算流程,支持基于Young-Laplace方程及Wenzel模型的表面能估算。已有576人学习下载,体现了其在实验数据处理中的实用价值。用户可直接调用脚本完成从原始图像读取、预处理、接触点识别到角度输出与表面能推算的全流程分析,代码结构清晰、模块分工明确,且内置基础错误处理机制,便于二次开发与适配不同实验条件下的液滴图像分析需求。 做材料表面分析的人,几乎都遇到过这类场景:从某个渠道下载了一个压缩包,文件名写着contact angle.rar,里面躺着一个contact_angle.m,附带一两张液滴照片。网络热词里也总能看到"matlab、contact angle"这对组合,说明大家都在找能直接跑通的接触角计算程序。但根据我的经验,这类程序十有八九第一次运行不是报错就是结果离谱,角度算出来 90 度上下浮动,连自己都怀疑是不是量角器都比它准。

这篇文章不是给你一份现成代码让你复制粘贴就完事,而是把这套程序从"能运行"变成"算得准"的全过程拆开讲清楚:文件里到底有什么、图像预处理哪些步骤省不得、接触角的几何定义是怎么落到atanpolyfit这些函数上的、以及跑完程序之后你凭什么相信那个数字。内容面向正在做接触角测量实验、需要批量处理液滴图片的工程师和科研人员,你不需要精通图像处理,但看完应该能独立把一套 MATLAB 程序改造得服服帖帖。

1. 解压后的第一件事:先搞清这个程序到底在算什么

1.1 接触角测量到底测的是什么

接触角(contact angle)指的是在固、液、气三相交界处,从固液界面经过液滴内部到气液界面切线所夹的角度。这个角度直接反映固体表面的润湿性:小于 90 度说明表面亲液,大于 90 度说明疏液。测量方式看着简单——把一滴液体滴在固体表面,从侧面拍张照片,量一下就行了。但真到程序实现时,那张照片里的信息量远比想象中多:液滴轮廓是一条曲线,固体表面是一条直线,接触角是两者交点的局部几何关系,而不是随便量一个整体角度。

很多网上下载的 MATLAB 程序,核心逻辑其实就三步:读图、找轮廓、算切线。只不过这三步每一步都有不少隐藏细节。contact_angle.m这个脚本通常不是一个人写的,它可能合并了多种方法,甚至留有原作者实验条件下的硬编码参数。你直接跑,很可能算的是"某个特定背光条件下、某个特定固体表面的角度",换一张图就失效。

1.2 文件构成与运行环境检查

解压contact angle.rar之后,典型的文件结构是这样的:

  • contact_angle.m:主脚本
  • compute_contact_angle.m:子函数,负责从边缘点坐标计算角度
  • droplet_sample.png.bmp:示例液滴图片
  • 可能还有一个README.txt或说明文档

运行前先检查环境,这步能省掉一半的报错。contact_angle.m普遍依赖 Image Processing Toolbox(用到imreadrgb2grayedgebwareaopen等函数)和 Curve Fitting Toolbox(用到polyfitpolyval)。在命令行敲ver查看工具箱列表,缺了就去装,否则一运行直接弹Undefined function 'edge'这种错误,很多人卡在这第一关。

另一个容易忽略的是路径。把整个文件夹放在纯英文路径下运行,别放桌面带中文的文件夹里。MATLAB 对中文路径的支持时好时坏,imread偶尔抽风读不出文件,排查起来非常浪费时间。我一般还会在脚本开头加上clc; clear; close all;,避免上次运行的变量残留在工作区里干扰结果。

提示:如果解压后没有示例图片,先别急着跑程序。找一张自己拍的液滴照片,背景尽量简单、液滴轮廓清晰,否则你根本分不清是代码问题还是图片问题。

2. 图像预处理这步决定成败:灰度化、降噪、背景分离

2.1 为什么要先转灰度再做边缘检测

相机拍出来的液滴照片是彩色图,每个像素有 R、G、B 三个通道。直接对彩色图做边缘检测不是不行,但三个通道分别响应,边缘会变得极不均匀——红光下液滴边缘清楚,蓝光下可能糊成一片。标准做法是先转成灰度图:

I = imread('droplet_sample.png'); if size(I, 3) == 3 I = rgb2gray(I); end

这一步把三维矩阵压缩成二维,之后的阈值分割、边缘检测全部基于亮度信息,计算量也小得多。实测下来,一张 1200 万像素的照片,灰度化后处理速度能提升三倍左右。

2.2 滤波窗口选多大才不会弄丢边缘

转灰度之后,紧接着是降噪。液滴照片的噪声来源通常有两类:一是相机传感器的高斯噪声,二是背景里的微小颗粒或灰尘产生的椒盐噪声。很多人一上来就用高斯滤波做平滑,结果液滴边缘被抹得圆乎乎,接触角算出来系统性地偏大或偏小。

我的习惯是先做中值滤波(medfilt2),窗口取 3×3,最多 5×5。中值滤波对椒盐噪声特别有效,而且能较好地保留边缘的陡峭程度。高斯滤波(imgaussfilt)不是不能用,但 sigma 超过 1.5 就容易把液滴顶部的小弧度细节吃掉,接触角测量对轮廓局部的切线方向极其敏感,边缘偏移哪怕一两个像素,角度就能差出好几度。

I_filt = medfilt2(I, [3 3]);

这里有个容易被忽略的点:滤波窗口越大,边缘越"钝"。做了 5×5 中值滤波之后,液滴边缘线会被轻微腐蚀,算出来的接触角往往比真实值偏大。所以滤波的原则是:能去掉噪声就行,窗口尽量小。如果原图噪声不大,直接跳过滤波也行。

2.3 自动阈值和 ROI 到底该信谁

接下来是二值化,把液滴从背景里分离出来。最简单的办法是imbinarize配合graythresh自动阈值:

BW = imbinarize(I_filt, graythresh(I_filt));

graythresh用的是 Otsu 方法,它会自动找一个阈值让前景和背景的类间方差最大。这个方法对"背景均匀、液滴和背景对比明显"的照片效果很好。问题在于,实际拍摄环境很难这么理想。如果光线从侧面打过来,液滴表面会有镜面反射,反光区域亮度接近背景,Otsu 会把那一块误判成背景,导致液滴轮廓缺个口子。

这种情况下,我不会完全信任自动阈值,而是先用imcrop把图像裁剪到液滴附近的区域。裁剪有两个好处:一是去掉画面边缘无关的高亮区域,让 Otsu 的阈值更准;二是大幅减少后续边缘检测的计算量。裁剪时不要裁得太紧,液滴下方要留出足够的固体表面区域,方便后面确定基线。

裁剪之后再次imbinarize或者edge,成功率会明显提升。如果背景依然不均匀(比如有渐变阴影),可以试试imbinarize(I_filt, 'adaptive'),它按局部区域计算阈值,对光照不均的图片比全局阈值稳得多。代价是速度慢,而且参数ForegroundPolarity需要根据液滴是亮还是暗来调整。

3. 从二值图到液滴轮廓:边缘检测与轮廓筛选

3.1 Canny 阈值怎么调才靠谱

二值图拿到手,下一步是提取液滴边缘。MATLAB 的edge函数支持多种算子,算接触角我强烈推荐 Canny。Sobel 算子边缘粗、对噪声敏感,算切线方向容易被局部毛刺带偏;Canny 做了非极大值抑制和双阈值,边缘细而且连续性好。

edges = edge(BW, 'Canny', [0.1 0.2]);

双阈值的理解很简单:低阈值决定边缘检测的灵敏度,高阈值决定保留哪些强边缘。[0.1 0.2]这个组合是大多数液滴图片的常用起点。如果检测出来的边缘断裂严重,说明高阈值太高,降到[0.05 0.15];如果边缘旁边跟着一堆杂线,说明低阈值太低,往上提到[0.15 0.3]

有个经验技巧:在调阈值之前,先把BW显示出来肉眼确认液滴在二值图里是不是完整的一块。如果二值图本身就有缺口,边缘检测调参怎么调都白搭。这时候应该回去调阈值分割,或者用形态学操作把缺口补上。

3.2 形态学处理与轮廓去杂

二值图里的液滴经常不是完美的一块,反光点会在液滴内部形成空洞,灰尘会在液滴旁边形成孤立的小白点。直接用bwboundaries提取轮廓,你会得到一串乱七八糟的边界,只有其中一条才是液滴轮廓。

形态学处理能解决大部分问题:

BW_filled = imfill(BW, 'holes'); % 填充液滴内部空洞 BW_clean = bwareaopen(BW_filled, 500); % 去掉小于500像素的孤立区域 BW_closed = imclose(BW_clean, strel('disk', 3)); % 连接断开的边缘

bwareaopen里的 500 是经验值,根据你的图片分辨率调整。如果液滴占了画面的大部分,这个值可以设成 2000 甚至更大;如果液滴本身就很小,设 500 可能把液滴主体给删了。判断标准很简单:处理后画面里应该只剩下一个最大的连通区域。

轮廓提取用bwboundaries

B = bwboundaries(BW_closed, 'noholes');

返回的B是一个元胞数组,每个元素是一圈边界的坐标点(N×2 的矩阵,第一列是行号 y,第二列是列号 x)。液滴轮廓通常是其中最长的那个边界。找出来后,用plot叠在原图上看一眼,确认轮廓是不是完整贴合液滴的外形。

3.3 基线(固体表面线)的确定方法

接触角的定义里,基线就是固体表面线。如果液滴是滴在一块平整的固体表面上,那么照片里固体表面就是一条接近水平的直线。这条线的位置直接决定了接触点的坐标,偏差哪怕半度都会让最终角度失真。

确定基线有三种方法,我按推荐程度排个序:

方法一:霍夫变换检测直线。用houghlines在边缘图里找线段,筛选角度接近 0 度(水平)的那条。这个方法自动但容易误检,液滴底部的弧线在某些角度下会被误判成水平线。

方法二:手动指定两个点。用ginput在图像上点击固体表面的左右两个位置,连成直线。这个最稳,但不适合批量处理。

方法三:拟合液滴轮廓的最低点区间。这个方法我用得最多——液滴与固体表面接触的那一段,轮廓与基线几乎重合。取轮廓点里 y 坐标最大的那部分点(大概是底部 5% 到 10% 的点),用polyfit(x, y, 1)做线性拟合,拟合出的直线就是基线。

idx = find(B_pts(:,1) > prctile(B_pts(:,1), 90)); p_base = polyfit(B_pts(idx,2), B_pts(idx,1), 1);

方法三的好处是完全自动,且物理意义明确:液滴底部被固体表面"压平"的那一段,天然就是基线的一部分。唯一的风险是液滴太小、底部弧线太弯,这时拟合出来的线会有点倾斜,需要人工检查。

基线确定后,轮廓与基线的交点就是左右两个接触点。这两个点的 x 坐标一个是最小值、一个是最大值,取轮廓点里离基线最近且 x 坐标分别最小和最大的点即可。

4. 接触角计算的几何本质与三种实现方案

4.1 图像坐标系下的角度陷阱

接触角的核心计算其实就是一个初中几何问题:曲线上某一点的切线斜率,决定了该点切线的角度。但 MATLAB 图像坐标系有一个大坑——原点在左上角,x 轴向右,y 轴向下。这跟我们熟悉的笛卡尔坐标系正好相反,导致atan算出来的角度方向全部翻转。

举个例子:液滴左侧轮廓,从接触点向上走,x 坐标逐渐增大(接触点在左,往右到液滴顶部),y 坐标逐渐减小(向上),在图像坐标系里斜率 dx/dy 是负的。用atan算出来的角度是负值,但是真实的物理角度是正的。这就是为什么很多新手程序算出来接触角一直在 0 到 10 度之间徘徊,以为固体表面超亲液,其实是坐标系符号没处理。

正确处理思路:先明确你要的角度是"接触点处液滴轮廓切线与水平基线之间的夹角",然后画个图理清几何关系。用atan2而不是atan,它能正确处理四个象限。算出切线向量后,再根据左右侧分别调整角度定义。

4.2 方案一:高宽比法(θ/2 法)——最快但有限制

高宽比法,也叫 θ/2 法,是接触角计算里最朴素的方法。它假设液滴是一个球冠(理想情况下液滴足够小,重力影响可忽略),此时接触角只跟液滴高度 h 和接触宽度 w 有关:

theta = 2 * atan(2 * h / w)

h 是液滴顶点到基线的垂直距离,w 是左右接触点之间的水平距离。用 MATLAB 实现就是先找到轮廓的最高点,再算高度和宽度。

这个方法的优点是代码量十几行,计算量几乎为零,而且对噪声不敏感。缺点是假设条件太苛刻:液滴必须足够小(一般直径小于 2 mm)才能忽略重力对形状的拉拽,而且液滴必须左右对称。实际实验里,液滴一旦稍微大一点,轮廓就被重力压扁,不再是球冠,高宽比法算出来的角度偏小。

我一般把 θ/2 法当快速预估工具用,批量处理几十张图先跑一遍,看看数据分布是否合理,再对关键样本用更精确的方法复核。

4.3 方案二:多项式拟合与切线法——工程界的主力方案

这是目前工程和科研里最常用的接触角算法,也是contact_angle.m这类程序里最可能采用的方法。核心思路是:把液滴左右两侧的轮廓分别提取出来,用多项式拟合轮廓曲线,然后在接触点处求导得到切线斜率,再换算成角度。

以左侧轮廓为例,代码逻辑是这样的:

% 提取左轮廓点:x 最小的那段轮廓 left_mask = (B_pts(:,2) == min(B_pts(:,2))) | ... (B_pts(:,2) < prctile(B_pts(:,2), 5)); % 这里只是示意,实际要按连通区间取左边缘点 x_left = B_pts(left_mask, 2); y_left = B_pts(left_mask, 1); % 二次多项式拟合 y = a*x^2 + b*x + c p = polyfit(x_left, y_left, 2); % 在接触点处求导:dy/dx = 2*a*x + b x_contact = min(x_left); slope = polyval(polyder(p), x_contact); % 切线角度(弧度转角度) theta_rad = atan(slope); % 图像坐标系翻转修正,左接触角 theta_left = 180 - abs(rad2deg(theta_rad)) + 90; % 按实际几何调整

这里有几个细节决定成败。第一,拟合阶数:二次还是三次?液滴轮廓在小范围内曲率变化不大,二次多项式够用;如果液滴偏大、轮廓明显不对称,用三次多项式更贴合。但阶数不是越高越好,四阶以上对噪声敏感,拟合曲线会故意穿过每个点的波动,导致接触点处的切线抖得很离谱。

第二,拟合点的范围。不是把整条左轮廓都拿去拟合,而是取接触点附近的一段——经典做法是取接触点往上约 20% 到 30% 的轮廓高度。范围选太长,远处的轮廓形状会影响切线方向;选太短,拟合不稳定。

第三,左右接触角要分开算。液滴不可能绝对对称,左右接触角有差异是正常的。左右各用各的接触点和轮廓段拟合,分别计算。这也是判断程序算得准不准的一个线索:如果左右角相差超过 10 度,大概率是基线定歪了,或者液滴滴歪了。

4.4 方案三:Young-Laplace 数值拟合——精度天花板

如果对精度要求极高,比如发论文、做标准测试,那就得用 Young-Laplace 方程拟合了。这个方程描述的是液滴外形在表面张力和重力共同作用下的平衡形状,是一个二阶常微分方程。思路是把测得的液滴轮廓与方程数值解做非线性最小二乘拟合,直接拟合出接触角。

MATLAB 实现通常用lsqcurvefit(需要 Optimization Toolbox)或fitnlm(需要 Statistics Toolbox)。核心难点在于初值选择和方程参数化,拟合变量通常包括液滴底部半径、接触角、表面张力与密度之比(毛细管长度)等。由于问题高度非线性,初值给得不好,拟合直接发散。

Young-Laplace 拟合对液滴大小没有限制,对大液滴特别准,因为它的物理模型完整考虑了重力对轮廓的压扁作用。但代价是代码量大、计算慢、参数敏感。我的建议是:工程应用和常规实验用方案二多项式拟合已经足够了,系数调好之后一致性很好;只有在需要追溯真值的场景才上 Young-Laplace。

5. 跑完程序之后:结果验证与工程化改造

5.1 怎么判断程序算得准不准

程序跑出数字不等于程序算对了。我拿到一个新接触角程序,测试靠谱程度只做三件事。

第一,把计算出的切线、基线和接触点画在原图上。这一步特别直观:看那条切线是否真的贴合液滴轮廓的走向,基线是否跟固体表面重合,接触点是否在固液气三相的交界处。代码里加几行:

figure; imshow(I); hold on; plot(B_pts(:,2), B_pts(:,1), 'g-', 'LineWidth', 2); plot([x_base1 x_base2], [y_base1 y_base2], 'r--', 'LineWidth', 2); plot(x_contact_left, y_contact_left, 'bo', 'MarkerSize', 8); % 画切线:沿斜率方向画一小段线段

只要看到的图跟物理直觉一致,那程序基本没问题。如果切线斜得离谱或者切在轮廓外面,赶紧回头检查拟合点范围。

第二,拿 ImageJ 手工量角对比。ImageJ 的接触角插件(如 DropSnake 或 LB-ADSA)是很多实验室的参考工具,拿同一张图在 ImageJ 里量一遍,如果 MATLAB 算出来的结果跟 ImageJ 差在 2 度以内,说明程序可信。差 3 度以上就说明算法里某个环节的系统性偏差没消除。

第三,重复性测试。同一张图跑十次,结果应该完全一致。批量处理时关注统计量而不是单张值,异常值(比如角度小于 5 度或大于 175 度)标记出来人工复核,而不是粗暴删掉。

5.2 批量处理:从单张到几百张图片

实验里往往是一次拍几十上百张图,不可能一张张手动跑。把单图脚本改造成批量版本,结构很简单:

files = dir(fullfile('droplet_images', '*.png')); results = table(); for k = 1:length(files) img = imread(fullfile(files(k).folder, files(k).name)); [theta_left, theta_right] = compute_contact_angle(img); results = [results; table(files(k).name, theta_left, theta_right, ... 'VariableNames', {'Filename', 'ThetaLeft', 'ThetaRight'})]; end writetable(results, 'contact_angle_results.xlsx');

批量处理里最容易翻车的是基线拟合。单张图手动确认没问题,但换一张光线稍暗的图,液滴底部拟合点范围选得不对,基线就歪了。我的经验是给批量脚本加一个"结果筛选条件":左右角差超过 15 度或者角度超出合理范围的样本,自动保存原图和处理后的轮廓图到另一个文件夹,等全部跑完再人工检查这些异常样本。

另外,批量处理前建议先把所有图片统一尺寸。不同相机、不同分辨率拍出来的图,bwareaopen的阈值和形态学结构元素大小都应该按比例调整。如果图片太大(比如 4000×3000),可以先imresize到宽 1000 像素左右,接触角本质是角度量,宽高比不变的情况下缩放不会改变角度值,但处理速度能快十几倍。

5.3 实操中踩过的坑与几个小提醒

最后说几个我在调试这类程序时踩过的坑,都是短视频教程不会提的细节。

一个是镜头的水平问题。相机必须正对液滴,镜头光轴与固体表面平行。如果相机有一点点俯仰,拍出来的液滴会变形,左右接触角一个偏大一个偏小。拍摄前用水平尺校准,比事后用算法修正靠谱得多。

另一个是液滴体积的选取问题。用微量注射器滴液滴,不同实验之间尽量保持体积一致。液滴太大,重力影响强,轮廓偏离球冠;液滴太小,蒸发速度快,拍摄时液滴可能在持续收缩。控制变量这件事,程序帮不了你,只能靠实验设计。

还有一个出乎意料的小坑:液滴内部的反光。如果光源位置不好,液滴表面会出现高光斑块,图像处理时这个反光区域的边缘可能被识别成第二层轮廓。形态学里imfill能填掉大部分反光造成的空洞,但如果是很大面积的高光,建议调整光源位置或者加扩散片,从源头上解决。

我自己在实际使用中养成的习惯是:所有接触角算法都不做"黑盒"使用。不管程序是从网上下载的还是自己写的,第一次跑通后,一定花半小时把每一步的中间结果画出来看一遍——灰度图、二值图、边缘图、轮廓图、最终切线图。这五张图全部确认没问题,批处理才有意义。接触角测量这件事,算法的上限取决于图像质量,程序只是把图像里的信息尽可能准确地提取出来。把样本拍好、把中间过程盯住,比到处找更高级的拟合算法要管用得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询