
简介这是一套基于MATLAB的分形盒维数计算代码包面向需要量化一维、二维、三维复杂结构自相似性的研究人员与工程技术人员解决盒计数法实现、覆盖盒子统计、维数拟合与精度核验等关键问题。包内共收录27个文件以四个M脚本为核心含boxcount、demo、随机康托尔生成并配有17张过程结果图、2个分支动图、HTML说明、数据库文件及JPG封面图整体仅3.15MB更便于下载后对照运行和检查。代码示例覆盖科赫曲线、曼德勃罗集、谢尔宾斯基海绵等典型分形附录对数-对数曲线拟合求斜率的完整过程用户可自行调整盒子尺度、分辨率与重复次数应用到自己的图像或点云数据中。目前已有683人学习适合初学分形几何并希望借助MATLAB快速复现维数计算的读者。1. 盒维数用网格丈量复杂度的实用工具当一条平滑曲线的长度用更小的尺子去测量时长度会趋近一个定值但如果测量的对象是一段噪声信号或一块粗糙的断口测量结果会随着尺子的变小而持续增长甚至趋于无穷。这个反直觉的现象恰恰是分形盒维数要刻画的核心——一个对象在不同观察尺度下表现出的结构复杂度。严格来说盒维数并不等于拓扑维度一条平滑曲线的盒维数接近 1而白噪声的盒维数能逼近 1.5一张纹理丰富的灰度图像可能从 2 逼近 2.3而三维孔隙结构可以落在 2.6 到 2.9 之间。维数越高说明对象在对应尺度上的自相似填充能力越强。这篇文章围绕「计算 1D、2D、3D 的分形盒维数」展开给出可复现的 Python 实现路径从数学定义到网格覆盖策略从一维信号到三维体数据再到回归拟合与误差控制。适合做表面粗糙度评估、医学影像纹理分析、材料孔隙率表征、地质断层复杂度研究的工程师。读完你能在自己的数据上跑出稳定的盒维数曲线并知道哪些参数不可乱调、哪些结果不可轻信。2. 盒维数的计算原理网格覆盖与双对数回归2.1 从定义到可执行的算法盒维数Box-Counting Dimension的严格定义是对于目标对象用边长为 ε 的网格覆盖它记覆盖所需的最少网格数量为 N(ε)。当 ε 趋近于 0 时如果存在如下极限关系D lim(ε→0) [ log N(ε) / log(1/ε) ]那么 D 就是对象的盒维数。实际计算中不可能让 ε 真正趋近于 0因为数据本身是离散采样的最小网格尺寸受限于像素或体素分辨率。工程上的做法是取一组递减的网格尺寸 ε对每个 ε 统计 N(ε)然后在双对数坐标系中拟合直线斜率就是盒维数的估计值。这里有一个常见误解需要澄清盒维数不是「用多少个盒子去数」这么简单它本质上是在问「当测量尺度变化时对象的细节以什么速率增长」。所以关键在于尺度的跨度。跨度太窄——比如只在 ε 1 和 ε 2 两个尺寸上统计——回归结果毫无意义。工程上我一般要求 ε 的最大值与最小值之间跨至少 4 个八度即 16 倍以上才能得到稳定的斜率估计。网格尺寸序列的选取直接决定计算结果的可靠性。我常见的做法是使用几何等比序列ε ε_max / 2^kk 0, 1, 2, ..., K其中 ε_max 取数据最大边长的一半K 取到单个像素为止。等比序列在双对数坐标上等距分布不会让回归点聚集在某一端。你可能会问为什么不用等差序列因为盒维数关心的是「尺度翻倍时细节的增量」等差序列会在大尺度端分布过密、小尺度端分布过疏回归权重失调。2.2 核心实现一个逐网格计数函数无论数据是 1D、2D 还是 3D核心操作都是同一个逻辑把数据空间划分为边长为 ε 的互不相交网格统计至少包含一个目标点的网格数量。下面用 Python 实现一个通用的计数函数import numpy as np def count_boxes(data, eps): 计算在给定网格尺寸 eps 下覆盖目标对象所需的盒子数量。 参数 ---------- data : np.ndarray, bool 类型 二值化后的数据。True 表示目标对象False 表示背景。 1D 形状为 (N,)2D 形状为 (H, W)3D 形状为 (D, H, W)。 eps : int 网格边长像素数必须为正整数。 返回 ---------- n_boxes : int 覆盖目标对象所需的盒子数量。 shape np.array(data.shape) # 每个维度上划分的网格数量向上取整保证边界不遗漏 grid_dims np.ceil(shape / eps).astype(int) # 初始化计数器 count 0 # 按网格逐个遍历 if data.ndim 1: for i in range(grid_dims[0]): block data[i*eps:(i1)*eps] if block.any(): count 1 elif data.ndim 2: for i in range(grid_dims[0]): for j in range(grid_dims[1]): block data[i*eps:(i1)*eps, j*eps:(j1)*eps] if block.any(): count 1 elif data.ndim 3: for i in range(grid_dims[0]): for j in range(grid_dims[1]): for k in range(grid_dims[2]): block data[i*eps:(i1)*eps, j*eps:(j1)*eps, k*eps:(k1)*eps] if block.any(): count 1 else: raise ValueError(仅支持 1D、2D、3D 数据) return count这个函数的逻辑很直接按网格边界切片检查目标对象是否出现在这个网格块内。有几个关键细节需要注意。第一np.ceil(shape / eps)而不是向下取整——当数据边长不能被 eps 整除时最后一行/列的网格尺寸小于 eps但必须纳入统计范围否则会低估 N(ε)。第二block.any()是布尔数组上的短路判断只要网格内至少有一个 True 就计数。第三返回值永远是整数这决定了后续线性回归必须在双对数空间做而不能直接对原始计数做多项式拟合。2.3 对数空间的线性回归斜率即维数有了count_boxes函数接下来就是遍历网格尺寸序列并对结果做线性回归。这里有一个容易出错的点np.log默认是自然对数np.log10是常用对数两者在回归中得到的斜率是相同的只需要注意绘图时坐标轴标尺与所用对数保持一致即可。我习惯统一使用np.log避免混用导致坐标刻度对不上。def box_dimension(data, max_epsNone, min_eps1, base2): 计算 1D/2D/3D 二值数据的盒维数。 参数 ---------- data : np.ndarray, bool 类型 二值化后的目标数据。 max_eps : int 或 None 最大网格尺寸。为 None 时取数据最长边的一半。 min_eps : int 最小网格尺寸默认 1 表示单个像素。 base : int or float 网格尺寸的等比序列底数默认 2。 返回 ---------- dim : float 盒维数估计值。 eps_list : list 实际使用的网格尺寸序列。 n_list : list 对应每个 eps 的盒子数量。 shape np.array(data.shape) max_len shape.max() if max_eps is None: max_eps max_len // 2 if max_eps min_eps: raise ValueError(max_eps 必须大于等于 min_eps) eps_list [] n_list [] eps max_eps while eps min_eps: n count_boxes(data, eps) if n 0: # 跳过计数为 0 的点避免 log(0) eps_list.append(eps) n_list.append(n) eps eps / base # 避免浮点误差导致死循环 if eps 1: break eps_arr np.array(eps_list, dtypefloat) n_arr np.array(n_list, dtypefloat) # 双对数线性回归斜率即盒维数 log_eps np.log(1.0 / eps_arr) log_n np.log(n_arr) slope, intercept np.polyfit(log_eps, log_n, 1) return slope, eps_list, n_list回归中我用了np.polyfit做一阶多项式拟合返回的斜率就是维数值。这里有一个细节要提醒polyfit默认对所有回归点等权处理但实际计算中小尺寸端的盒子数量大、统计噪声相对小大尺寸端的盒子数量少、容易受边界效应干扰。如果你发现拟合结果对网格序列的选择特别敏感可以考虑只取中间一段进行回归或者用加权最小二乘。不过对于大多数工程场景等权线性回归已经够用过度拟合反而掩盖了真实误差。2.4 为什么不用盒数直接做除法有人会问维数能不能简单用N(ε)/N(2ε)的比值估算如果对象严格自相似可以但真实数据几乎不满足严格自相似。用局部比值估算的方差极大就像用两个点拟合一条直线噪声完全没被平滑掉。线性回归的意义在于用整个尺度序列的全局趋势来压低单个尺度处的涨落。这也是我在实际项目中坚持用回归法而不是比值法的原因。另外关于「最小网格尺寸」工程上不建议直接取 1。当 ε 1 时每个像素是一个独立网格N(1) 等于目标对象的像素总数这个数据点只在目标对象极其稀疏时携带信息。更稳妥的做法是令最小网格尺寸为 2 或 4确保每个网格内包含至少 2×2或 2×2×2个像素避免单像素噪声主导回归。具体的选取经验我会在第 5 章展开。3. 从一维信号到二维图像逐级上手的完整实现3.1 一维信号的盒维数噪声与趋势的定量区分一维信号的盒维数最常见的应用场景是区分「平滑信号」和「噪声信号」一条光滑曲线或趋势信号的盒维数接近 1.0白噪声的盒维数接近 1.5而一条带有毛刺的间歇信号的维数落在 1.1 到 1.3 之间。这个指标比均方根误差更敏感——均方根只刻画幅度盒维数刻画的是「细节随尺度的增长速率」。一维信号计算的前提是二值化。原始信号是浮点数组需要先转换成布尔数组。常见做法包括阈值分割、局部极值标记、或者设定一个幅度带。以阈值分割为例import numpy as np def signal_to_binary(signal, thresholdNone, modeabove): 将一维浮点信号转换为布尔标记。 参数 ---------- signal : np.ndarray, 1D 输入信号。 threshold : float 或 None 阈值。为 None 时使用信号均值。 mode : str above 表示大于阈值记为 True below 表示小于阈值记为 True interval 表示在 [threshold_lo, threshold_hi] 内记为 True。 返回 ---------- binary : np.ndarray, bool, 1D if threshold is None: threshold np.mean(signal) if mode above: return signal threshold elif mode below: return signal threshold elif mode interval: lo, hi threshold return (signal lo) (signal hi) else: raise ValueError(mode 必须是 above、below 或 interval)二值化的选择会直接影响维数估计——这一点在第 5 章专门讲。这里先说明一个通用原则阈值的选取应该基于信号的物理意义而不是简单地用均值。比如分析肌电信号你可以用信号包络的某个分位数分析地震波可以用振幅的绝对值。均值阈值只适合信号在零附近对称分布的场景。整个 1D 流程封装起来def box_dimension_1d(signal, thresholdNone, modeabove, max_epsNone, min_eps2): 一维信号盒维数的完整计算流程。 binary signal_to_binary(signal, threshold, mode) # 确保输入是布尔类型且至少有一个 True if not binary.any(): raise ValueError(二值化后目标点为空请调整阈值) dim, eps_list, n_list box_dimension(binary, max_epsmax_eps, min_epsmin_eps) return dim, eps_list, n_list注意min_eps我设为了 2而不是 1。原因是一维信号长度为 N 时ε1 的盒子数恰好等于目标点数它携带的信息只是稀疏度与分形尺度行为无关。从 ε2 起步可以避免这一非尺度项的干扰。3.2 二维图像的盒维数灰度二值化与网格偏移二维图像是盒维数最常用的场景。常见流程是读入灰度图 → 二值化 → 对每个 ε 计数 → 回归求斜率。下面是完整实现import numpy as np from skimage import io, color from skimage.filters import threshold_otsu def image_box_dimension(image_path, thresholdotsu, min_eps2, max_epsNone): 二维灰度图像的盒维数计算。 参数 ---------- image_path : str 图像文件路径。 threshold : str 或 float otsu 使用 Otsu 自动阈值固定阈值直接传浮点数。 min_eps : int 最小网格尺寸默认 2。 max_eps : int 或 None 最大网格尺寸None 时取图像短边的一半。 返回 ---------- dim : float 盒维数估计值。 if image_path.lower().endswith((.tif, .tiff)): img io.imread(image_path, as_grayTrue) else: img color.rgb2gray(io.imread(image_path)) # 二值化 if threshold otsu: thr threshold_otsu(img) else: thr float(threshold) binary img thr # 兜底检查目标占比过低或过高时告警 ratio binary.mean() if ratio 0.01 or ratio 0.99: print(f警告目标像素占比 {ratio:.3f}结果可能不可靠) dim, eps_list, n_list box_dimension(binary, max_epsmax_eps, min_epsmin_eps) return dim, eps_list, n_list这段代码里有一个容易忽视的选择max_eps默认取图像短边的一半而不是长边。原因在于网格必须是正方形当 ε 接近长边时网格在某些维度上已经覆盖整个数据范围这时计数信息退化为「是否存在对象」对维数估计没有贡献。取短边的一半可以保证至少有 2×2 个网格覆盖数据区域。我在实际处理灰度图时还会额外做一个预处理步骤先对图像做一次高斯平滑σ 1 到 2 像素再求灰度直方图。平滑的目的是压制单像素成像噪声——这类噪声会把盒维数系统性抬高 0.05 到 0.15。如果你处理的图像本身就是低噪声的高质量扫描图可以跳过这一步。3.3 灰度图像的高阶选择直接算灰度曲面还是先二值化一个常见的进阶问题是灰度图像能不能不二值化、直接把灰度曲面当作一个 2.5 维对象来处理答案是「可以但需要换一个算法」。直接把灰度值当作高度场然后计算这个曲面在 3D 空间中的盒维数需要把每个像素 (i, j, intensity) 扩展为一个点云然后做三维网格计数。这在原理上没问题但计算量陡增且灰度值的线性缩放会改变维数——这是因为盒维数对坐标轴的单位敏感如果 z 轴的尺度相对于 x/y 轴过大曲面会被压扁或拉成陡壁维数随之失真。因此我的工程建议是除非你有明确的物理场景要求比如 AFM 形貌图否则优先使用二值化路径。灰度纹理信息可以通过「多阈值扫描」来捕捉——在第 6 章我会展开这一技巧。3.4 从 2D 到 2.5D边界情况的处理处理图片时还有一类特殊对象单像素宽曲线比如血管骨架、裂纹路径。对这些对象盒维数理论上接近 1但直接套用 2D 网格计数会遇到一个坑当 ε 较小时单像素线在每个网格中最多贡献 1 个盒子当 ε 增大时一条跨越多个网格的对角线会产生比水平线多近一倍的盒子数。这不是算法错了而是直线的方向性影响了计数。解决办法是旋转图像取多个角度计算并取平均或者直接接受方向性偏差并记录在报告中。4. 三维体数据的盒维数体素输入、内存边界与体素化陷阱4.1 三维计算的特殊性不只是多了一层循环三维盒维数的计算逻辑与一维、二维完全一致但有两个工程问题在三维场景下被放大。第一是计算量三维数据体素数量是边长的三次方逐个网格遍历时会大量重复访问内存直接套用 Python 三重循环会非常慢。第二是数据形态医学影像、CT 扫描、材料重建体数据往往是灰度体数据二值化前需要先做分割或阈值处理这比二维图像更难做。在动手之前先明确计算三维盒维数的最佳输入是二值体数据布尔数组而不是原始灰度体数据。如果你手里只有灰度体数据必须先分割出目标区域。以医学 CT 中的孔隙分析为例常见流程是读取 NIfTI 或 HDF5 格式的体数据 → 用全局阈值或区域生长分割 → 生成布尔体数组 → 再算盒维数。4.2 三维网格计数的加速实现对于三维数据纯 Python 三重循环的性能瓶颈很严重。一个 512³ 的体数据在 ε2 时需要遍历 256³ ≈ 1678 万个网格块每个块还要做切片和 any() 判断纯 Python 实现可能需要几分钟。我一般用下面两种方式加速。第一种用stride_tricks把体数据切块后用any聚合import numpy as np from numpy.lib.stride_tricks import as_strided def count_boxes_3d_fast(data, eps): 三维二值体数据的盒计数使用滑窗视图加速。 参数 ---------- data : np.ndarray, bool, 3D eps : int 网格边长。 返回 ---------- count : int d, h, w data.shape gd, gh, gw int(np.ceil(d / eps)), int(np.ceil(h / eps)), int(np.ceil(w / eps)) # 填充到网格边界的整数倍 pad_d gd * eps - d pad_h gh * eps - h pad_w gw * eps - w data_padded np.pad(data, ((0, pad_d), (0, pad_h), (0, pad_w)), modeconstant, constant_valuesFalse) # 视图形状: (gd, eps, gh, eps, gw, eps) strided as_strided( data_padded, shape(gd, eps, gh, eps, gw, eps), strides( eps * data_padded.strides[0], data_padded.strides[0], eps * data_padded.strides[1], data_padded.strides[1], eps * data_padded.strides[2], data_padded.strides[2] ) ) # 合并网格内的 eps 维: (gd, gh, gw) block_any strided.transpose(0, 2, 4, 1, 3, 5).reshape(gd, gh, gw, -1).any(axis-1) return int(block_any.sum())这段代码的核心是as_strided生成的视图不复制数据然后把网格内的所有元素合并到最后一维做any判断。对比纯循环版本速度提升通常在 10 倍到 50 倍之间。需要特别注意strides参数必须手动计算一旦填错会直接读越界内存。这个实现依赖数据是 C 连续的默认的 numpy 数组都是如果不是先调用np.ascontiguousarray(data)。第二种如果不想碰as_strided可以用skimage.measure.block_reducefrom skimage.measure import block_reduce def count_boxes_3d_blockreduce(data, eps): data_padded np.pad( data, ((0, (-data.shape[0]) % eps), (0, (-data.shape[1]) % eps), (0, (-data.shape[2]) % eps)), modeconstant, constant_valuesFalse ) block_max block_reduce(data_padded.astype(np.uint8), block_size(eps, eps, eps), funcnp.max) return int((block_max 0).sum())block_reduce的效率略低于手写as_strided但代码可读性高很多适合在项目初期用。我个人的习惯是临时分析用block_reduce接入生产流水线时改写成as_strided。4.3 三维数据的边界填充策略三维网格计数时数据边长几乎不可能恰好是 ε 的整数倍。前面两个实现都用了np.pad把数据填充到整数倍网格。这里的填充值必须是False背景绝不能填True目标。一个容易踩的坑是用np.pad默认的constant_values0填充布尔数组时0 会转成 False没问题但如果直接把填充值写成True边界处会人为增加一批目标盒子导致维数被高估。另一个值得注意的点当 ε 接近数据边长时比如 ε 256 之于 512³ 数据填充后的网格总数量只有 8 个这 8 个网格的计数受目标对象在边界处的具体分布影响极大。这就是为什么我在box_dimension中建议max_eps取边长的一半——超过这个值回归点的统计涨落会显著增大。4.4 体数据的其他形态点云与三角面片有时你手里的三维数据不是规则体素而是点云比如激光扫描得到的裂缝面或三角网格比如 CT 重建后的表面。对这些数据常规做法是把点云投影到三维网格上生成二值体数据然后再算盒维数。投影前需要确定网格分辨率也就是「把多少个点合并成一个体素」。这个分辨率的选择本质上决定了盒维数的截断尺度——投影网格太粗小尺度细节丢失太细点云稀疏处全是空洞。工程上我会用点云的平均点间距作为体素尺寸的下限参考。如果平均点间距是 0.5 mm体素尺寸设为 0.5 mm 到 1 mm 之间比较合理。更小没有意义因为那个尺度下根本没有采样点。这个「数据分辨率决定最小计算尺度」的意识是整个盒维数工程实践里贯穿始终的核心思想。5. 避坑指南四个让盒维数计算翻车的经典问题5.1 网格偏移导致的人为周期性现象对同一条二值曲线把图像整体平移 1 个像素后重算维数跳变了 0.08。网格计数出现明显的锯齿状波动。原因固定网格对齐方式时细长对象如单像素直线、狭窄裂缝可能刚好落在网格边界上或刚好跨过网格边界盒子数量随偏移周期性变化。这不是数据的问题而是采样网格与对象之间的相位关系没有被平均掉。解决对每个 ε在 x 和 y 方向分别引入 0 到 ε-1 的随机偏移重复计数并取平均值。更简单的方案是直接对原始数据做若干次随机平移后取维数平均值。工程上考虑到计算成本我通常只做 4 到 8 次随机偏移平均。这个操作能显著降低单次网格对齐带来的方差特别是对 1D 信号和细线状目标。5.2 最小网格尺寸的选取不当现象同一张图min_eps1时维数为 2.31min_eps4时为 2.18。结果对参数极其敏感。原因ε1 时盒子数等于目标像素总数这个数据点完全由目标占比决定不反映任何尺度行为。回归时它会把斜率往「低尺度端异常值」方向拉。反过来说如果min_eps取太大比如 16小尺度细节全部被抹平维数会向拓扑维度方向偏移。解决min_eps 建议取 2 到 4。如果目标对象是光滑大块结构占比超过 30%取 4如果目标是稀疏细线占比低于 5%取 2。一个通用的兜底策略是跑一组 min_eps [1, 2, 4, 8] 的维数序列画成曲线观察平台区间取平台区间的结果作为最终值。5.3 灰度二值化阈值敏感维数随阈值漂移现象同一张灰度纹理图阈值取 Otsu 时维数 2.26阈值取均值时 2.41阈值取均值减去一个标准差时冲到 2.68。原因阈值越低被标记为目标的像素越多包括大量暗部噪声和次边缘像素这些像素在小尺度上贡献了大量盒子。如果阈值没有物理依据维数就变成了阈值的函数。解决不要用「把维数调到某个理想值」来选择阈值。正确的做法是做阈值扫描从灰度直方图的 P10 到 P90 分位数取一组阈值分别计算维数画出维数-阈值曲线。如果曲线在某个区间内平稳取平稳段的维数均值如果整个曲线单调变化没有平台说明图像中目标和背景的边界并不清晰此时盒维数本身缺乏稳定语义应该在报告中注明「维数对阈值敏感结果仅作相对比较用」。灰度直方图的开局观察在这时候很重要——单峰分布基本意味着阈值分割不适用。5.4 log(0) 与零计数的静默失败现象程序没有报错但维数算出来是 0.9 或者 3.2明显不合理。检查回归点发现有一两个 ε 对应的盒子数为 0。原因当目标对象极其稀疏比如三维体数据中孔隙率只有 0.1%大网格尺寸下某些网格内可能完全没有目标any()判断全 False。如果box_dimension函数没有跳过这些点np.log(0)会产生-infpolyfit会把inf当作一个正常点参与拟合。解决在计数循环中显式跳过n 0的点前面给出的box_dimension实现里已经加了if n 0的判断。但这只是兜底更本质的问题是当回归点数量少于 4 个时拟合结果没有任何统计意义。因此在计算完成后要检查回归点的数量——至少有 5 个点且覆盖至少 3 个八度结果才值得写入报告。我在实际代码里会加上这样的断言if len(eps_list) 5: raise ValueError(f有效回归点只有 {len(eps_list)} 个结果不可信)这个检查能拦截掉一批「看似算出维数、实际是噪声拟合」的结果。5.5 目标占比极端时的结果可靠性现象一张 1024×1024 的图里目标只有几十个像素算出的维数在 1.0 左右但换一张相似的图维数变成了 1.8。原因目标过于稀疏时盒子数统计受单个像素分布影响极大不再反映对象的几何结构而是反映采样密度。这时盒维数接近 1稀疏点集的一维特征是必然的但这个数字没有任何物理意义。解决在二值化后、计算前先检查目标占比。经验阈值是2D 图目标占比低于 1%3D 体数据目标占比低于 0.1% 时盒维数结果只能作为定性参考。不要在汇报时把这种数字当作可靠的材料参数。更合理的选择是对目标区域做裁剪放大目标区域的相对占比后再算。6. 进阶验证用已知维数的基准数据校准你的实现最后一章我留给验证方法——这是整个流程里最容易被跳过的环节。直接用真实数据跑出维数就交报告等于把自己的实现建立在毫无对照的假设之上。工程上正确的做法是先造一批已知维数的基准数据验证代码结果的误差在一个可接受范围内再上真实数据。最常用的基准对象包括三类。第一是直线段其盒维数严格为 1.0第二是实心正方形或立方体维数分别为 2.0 和 3.0第三是严格自相似的 Cantor 集和 Sierpinski 三角形理论维数分别为 log(2)/log(3) ≈ 0.6309 和 log(3)/log(2) ≈ 1.5850。前两类验证实现正确性第三类验证回归流程在非平凡分形上的表现。以 2D 为例生成一个 512×512 的 Sierpinski 三角形并计算import numpy as np from scipy.ndimage import zoom def generate_sierpinski(size512, depth6): 生成 Sierpinski 三角形二值图像size 需为 2 的幂。 # 初始化三角形顶点 x np.zeros(size) y np.zeros(size) # 用迭代函数系统生成点集简化实现 points [] for _ in range(size * size): choice np.random.randint(3) if choice 0: x x / 2 y y / 2 elif choice 1: x (x 1) / 2 y y / 2 else: x x / 2 y (y 1) / 2 points.append((x, y)) # 这里只是占位实际应使用更高效的网格细分法 return points实际的 Sierpinski 三角形生成建议用标准的迭代函数系统IFS或递归细分法而不是随机撒点因为随机撒点会引入颗粒噪声使得测量维数偏离理论值。生成基准数据后运行你的box_dimension函数标准误差在 0.03 以内即认为实现可靠。另一个进阶技巧是多阈值扫描与分段回归。真实材料或医学图像往往是多尺度的大尺度呈现片状或块状结构小尺度呈现颗粒或纹理结构。这时全尺度上的单一回归斜率可能处于两种结构的过渡区得到一个没有明确物理意义的中间值。处理方法是先绘制log N(ε) vs log(1/ε)曲线观察是否有明显的斜率转折点。如果曲线在某个 ε 处出现折点用分段线性回归分别拟合两段的斜率各自对应不同尺度下的盒维数。折点常常对应材料中颗粒尺寸或纹理基元尺寸这个信息本身可能比维数更有价值。最后回到实践习惯。我早期在灰度图像上直接用固定阈值处理结果维数冲到 2.8后来检查灰度直方图才发现是阈值选太低、噪声斑块全被当成目标纹理。现在每次计算之前我都会先看一眼数据形态——1D 信号先画时域波形2D 图像先看直方图3D 体数据先做几个切面的最大密度投影然后再谈参数和维数。这套流程看着笨但省下的返工时间远大于多花的几分钟。希望这篇笔记能帮你在计算盒维数时少走几段弯路。本文还有配套的精品资源点击获取