> 技术文档 > 提取动漫图像轮廓并拟合为样条曲线(MATLAB)

提取动漫图像轮廓并拟合为样条曲线(MATLAB)


前言

之前有看到B站上的up主进行人工拟合,然后看到有评论说用MATLAB进行拟合的,就用deepseek生成了一下,只能说费时费力的感觉,一个是对于精度高的图像拟合效果特别差(大家可以用自己的证件照试一下),就比如用ai进行模糊照片的优化,他可以增加图片的像素点,但可能出现的结果不像自己;另一个只能识别轮廓,对于不同的图片还需要调试显示的阈值,之前就出现过把纯色背景也出现线条的,而用动漫图片进行处理,可能就是结果也不是很像。

而且对于生成的函数还不知道如何导入desmos,他虽然生成了一堆函数,但是我还不知道如何使用,自己试了十多分钟发现结果近似一条直线。

example | Desmoshttps://www.desmos.com/calculator/h2wsnoy3xi不知道是不是下面的这种方法,我是(x(t),y(t)),t的范围这样的形式,感觉不太对。

使用Python语言进行函数作画绘制芙莉莲&勇者-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/148412637?spm=1001.2014.3001.5501使用Python进行函数作画-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/148383559?spm=1001.2014.3001.5501形态学图像处理_形态学图像处理python-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/138317780?ops_request_misc=%257B%2522request%255Fid%2522%253A%25229d7c4289359b3d8883f431656d0460a1%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=9d7c4289359b3d8883f431656d0460a1&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-2-138317780-null-null.nonecase&utm_term=%E8%BD%AE%E5%BB%93&spm=1018.2226.3001.4450Canny算子-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/139259820?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522128e956ccfd1321812df3ab3036e6f90%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=128e956ccfd1321812df3ab3036e6f90&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-139259820-null-null.nonecase&utm_term=%E7%AE%97%E5%AD%90&spm=1018.2226.3001.4450

代码

% 清除工作区和关闭所有图形窗口clear; close all; clc;%% 1. 图像读取与高级预处理imageName = \'example.png\'; % 图片文件名[~, name, ~] = fileparts(imageName); % 提取不带扩展名的文件名originalImg = imread(imageName);figure; subplot(2,3,1); imshow(originalImg); title(\'原始图像\');% 转换为灰度图grayImg = rgb2gray(originalImg);% 增强图像对比度以提高边缘检测效果grayImg = imadjust(grayImg);% 使用高斯滤波减少噪声grayImg = imgaussfilt(grayImg, 1.5);% 使用多种方法结合进行二值化% 方法1: Otsu\'s 方法level_otsu = graythresh(grayImg);bw_otsu = imbinarize(grayImg, level_otsu);% 方法2: 自适应阈值bw_adaptive = imbinarize(grayImg, \'adaptive\', \'Sensitivity\', 0.5);% 结合两种方法bwImg = bw_adaptive | bw_otsu;% 使用形态学操作清理图像% 先去除小对象(噪声)min_object_size = 100; % 增加最小对象大小bwImg = bwareaopen(bwImg, min_object_size);% 填充小孔洞bwImg = imfill(bwImg, \'holes\');% 使用开运算平滑边缘se = strel(\'disk\', 3);bwImg = imopen(bwImg, se);% 使用闭运算连接断开的边缘se = strel(\'disk\', 2);bwImg = imclose(bwImg, se);subplot(2,3,2); imshow(bwImg); title(\'优化后的二值图像\');%% 2. 高级边缘检测% 使用多尺度边缘检测edgeImg1 = edge(grayImg, \'canny\', [0.05 0.2], 1.0); % 细边缘edgeImg2 = edge(grayImg, \'canny\', [0.1 0.3], 2.0); % 粗边缘% 结合多尺度边缘edgeImg = edgeImg1 | edgeImg2;% 使用Sobel算子增强边缘检测sobelImg = edge(grayImg, \'sobel\', 0.05);edgeImg = edgeImg | sobelImg;% 清理边缘图像edgeImg = bwareaopen(edgeImg, 20); % 去除小边缘edgeImg = imdilate(edgeImg, strel(\'disk\', 1)); % 稍微膨胀边缘subplot(2,3,3); imshow(edgeImg); title(\'多尺度边缘检测\');% 结合二值图像和边缘图像combinedEdges = edgeImg;% 只保留在二值图像中存在的边缘区域combinedEdges = combinedEdges & imdilate(bwImg, strel(\'disk\', 5));subplot(2,3,4); imshow(combinedEdges); title(\'结合边缘\');%% 3. 轮廓追踪% 填充小孔洞,使轮廓更完整filledEdges = imfill(combinedEdges, \'holes\');% 使用bwboundaries追踪轮廓B = bwboundaries(filledEdges, \'noholes\');% 可视化轮廓subplot(2,3,5); imshow(originalImg);hold on;for k = 1:length(B) boundary = B{k}; % 只绘制足够长的轮廓 if size(boundary, 1) > 50 plot(boundary(:,2), boundary(:,1), \'r\', \'LineWidth\', 1); endendtitle(\'追踪到的轮廓\');hold off;%% 4. 智能关键点提取simplifiedBoundaries = cell(size(B));tolerance = 0.8; % 增加简化容差,减少关键点数量for k = 1:length(B) boundary = B{k}; % 只处理足够长的轮廓 if size(boundary, 1)  10 % 计算轮廓总长度 total_length = 0; for i = 2:size(boundary, 1) total_length = total_length + norm(boundary(i,:) - boundary(i-1,:)); end % 根据轮廓长度动态调整简化程度 dynamic_tolerance = tolerance * (total_length / 200); % 简化轮廓 simplified = [boundary(1,:)]; for i = 2:size(boundary, 1)-1 if norm(boundary(i,:) - simplified(end,:)) > dynamic_tolerance simplified = [simplified; boundary(i,:)]; end end simplified = [simplified; boundary(end,:)]; simplifiedBoundaries{k} = simplified; else simplifiedBoundaries{k} = boundary; endend%% 5. 高级曲线拟合figure; imshow(ones(size(grayImg))); % 创建一个空白画布hold on;axis equal;set(gca, \'YDir\', \'reverse\'); % 调整坐标方向,使其与图像坐标一致% 设置坐标轴范围与原始图像一致axis([1 size(grayImg, 2) 1 size(grayImg, 1)]);% 存储所有拟合函数splineFunctions = cell(length(simplifiedBoundaries), 1);for k = 1:length(simplifiedBoundaries) if isempty(simplifiedBoundaries{k}) continue; end keyPoints = simplifiedBoundaries{k}; if size(keyPoints, 1) < 4 % 样条拟合至少需要4个点 continue; end % 提取x和y坐标 x = keyPoints(:, 2); y = keyPoints(:, 1); % 参数化样条拟合 points = [x\'; y\']; splineCurve = cscvn(points); % 存储拟合函数 splineFunctions{k} = splineCurve; % 绘制拟合后的曲线 fnplt(splineCurve, \'b\', 2);endtitle(\'用样条曲线拟合的动漫轮廓\');hold off;%% 6. 输出拟合函数信息并保存到文件outputFileName = [name \'.txt\'];fid = fopen(outputFileName, \'w\');% 计算非空曲线数量nonEmptyCount = sum(~cellfun(@isempty, splineFunctions));fprintf(\'共拟合了%d条曲线\\n\', nonEmptyCount);fprintf(fid, \'图像 \"%s\" 的轮廓拟合函数\\n\\n\', imageName);fprintf(fid, \'共拟合了%d条曲线\\n\\n\', nonEmptyCount);curveIdx = 1;for i = 1:length(splineFunctions) if ~isempty(splineFunctions{i}) fprintf(\'曲线%d: \', curveIdx); fprintf(fid, \'曲线%d:\\n\', curveIdx); % 显示样条的基本信息 breaks = splineFunctions{i}.breaks; pieces = splineFunctions{i}.pieces; order = splineFunctions{i}.order; dim = splineFunctions{i}.dim; coefs = splineFunctions{i}.coefs; fprintf(\'分段数: %d, 阶数: %d, 维度: %d\\n\', pieces, order, dim); fprintf(fid, \'分段数: %d, 阶数: %d, 维度: %d\\n\', pieces, order, dim); % 检查coefs的维度 [rows, cols] = size(coefs); % 修复系数矩阵维度问题 if cols ~= pieces fprintf(\'警告: 系数矩阵的列数(%d)与分段数(%d)不匹配,尝试调整\\n\', cols, pieces); fprintf(fid, \'警告: 系数矩阵的列数(%d)与分段数(%d)不匹配,尝试调整\\n\', cols, pieces); % 尝试修复:取较小的值作为实际分段数 actual_pieces = min(cols, pieces); fprintf(\'使用实际分段数: %d\\n\', actual_pieces); fprintf(fid, \'使用实际分段数: %d\\n\', actual_pieces); else actual_pieces = pieces; end % 输出每段的函数表达式 for j = 1:actual_pieces % 参数范围 t_start = breaks(j); t_end = breaks(j+1); % 提取当前段的系数 - 修复索引问题 if dim == 2 % 二维样条:x和y分量 % 确保索引不超出范围 if order <= rows  x_coefs = coefs(1:order, j);  if 2*order  1e-10 % 忽略非常小的系数 if ~isempty(expr) && coef > 0 expr = [expr \' + \']; elseif ~isempty(expr) && coef < 0 expr = [expr \' - \']; coef = -coef; elseif isempty(expr) && coef < 0 expr = [expr \'-\']; coef = -coef; end if power == 0 expr = [expr num2str(coef, \'%.6f\')]; elseif power == 1 if abs(coef - 1) < 1e-10  expr = [expr var_name]; else  expr = [expr num2str(coef, \'%.6f\') \'*\' var_name]; end else if abs(coef - 1) < 1e-10  expr = [expr var_name \'^\' num2str(power)]; else  expr = [expr num2str(coef, \'%.6f\') \'*\' var_name \'^\' num2str(power)]; end end end end if isempty(expr) expr = \'0\'; endend