十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MATLAB图像风格化:彩铅、油画与低多边形算法实现

MATLAB图像风格化:彩铅、油画与低多边形算法实现 简介这份开题报告PDF聚焦图片艺术风格化课题适合计算机视觉、数字媒体技术等专业的本科生或研究生在准备开题、设计图像风格化项目时参考。报告围绕彩铅、油画和低多边形三种主流艺术风格展开完整覆盖研究背景、研究内容、研究方法、可行性分析、时间安排和参考文献六个部分其中彩铅效果通过生成笔画结构与色调图实现油画效果基于像素邻域强度分析取重复像素值输出低多边形效果则借助图像边缘提取与Delaunay三角网构建完成技术刻画较为具体。全文采用Matlab作为编程环境并规划了5至10周的渐进式开发节奏。资源为单个PDF文件大小仅123KB内容精炼且信息密度高适合需要快速把握图像风格化项目要点、借鉴开题报告章节框架的读者。目前已有68人学习下载无论是课程立项还是自主研究都能从中获得可复用的思路和实现路径。1. 图片艺术风格化的研究边界滤镜与算法的距离图像早已取代长文本成为互联网环境下人们接受信息、表达情绪的首要载体。打开任何一款修图软件滤镜少则几十、多则上百选中一个点下去就出效果。可滤镜背后的算法差异远比界面缩略图里的那点视觉差别要大。图片艺术风格化不能简单理解成调亮度或加对比度它是在一张照片上重建笔触、颗粒与色块让程序输出看起来像人画出来的。这次拆解的项目选了彩铅、油画和低多边形三种风格在 MATLAB 里分别实现算法、再整合成带 GUI 的程序全程涉及灰度变换、边缘检测、邻域统计和 Delaunay 三角剖分。无论你是要做图像处理课程设计还是给现有图像管线增加风格化能力这套从原理到代码的路径都能直接照着跑。2. 三种风格化算法的原理彩铅、油画与低多边形2.1 彩铅效果的本质笔画结构、色调图与色调渲染图彩铅效果实际上是三张特征图逐层叠加的结果。第一步产生笔画结构图它来自边缘检测得到的二值边界再用高斯核把边界向外扩散让线条拥有宽度和浓淡过渡第二步生成色调图把灰度亮度划分成有限层级模拟下笔的深浅差异第三步将色调图转化为色调渲染图常见做法是做一次模糊或对比度压缩让过渡区域更自然。最后把笔画结构图与色调渲染图融合在笔画位置压暗、非笔画位置保留色调再结合原图色相输出彩色铅笔画。从实现看这一步并不复杂。假设笔画结构图为 S(x,y)色调渲染图为 T(x,y)融合可以写成一行的矩阵运算% 彩铅融合的核心笔画处压暗非笔画保留色调 pencil T .* (1 - alpha .* S); % alpha 控制线条压暗强度建议范围 [0.5, 0.9]这里的 alpha 是压暗系数。取值太小时线条浮在画面上与色调脱节取值太大时暗部面积过多画面发脏。调参时记住一个原则alpha 是最后动的参数先决定笔画密度和色调层级再回头微调它。2.2 油画效果的本质亮度分箱与邻域众数油画效果的基本单位是区域色块。对图像中的每个像素取邻域窗口内的全部像素将亮度划分为若干个分箱bin统计每个分箱的出现次数选中次数最多的亮度分箱再把该分箱内所有像素的 R、G、B 分量分别求平均作为当前像素的输出值。这个过程本质上是一种众数滤波。它和均值滤波、中值滤波有本质差别均值滤波保留渐变色但整体变糊中值滤波擅长去椒盐噪声但保留边缘结构油画效果则刻意放大邻域内的多数颜色让画面出现类似笔刷反复涂抹后的钝感与堆积感。实现时只关注两个参数邻域窗口大小 winSize 和亮度分箱数量 binNum。winSize 决定笔触的物理尺寸binNum 决定色块的频度。窗口越大、分箱越少画面越块面化窗口小、分箱多则接近局部对比度增强。对一张 1080p 图片winSize 超过 15 后视觉变化开始趋缓收益不大但耗时成平方增长。2.3 低多边形效果的本质Delaunay 三角网与边缘约束低多边形风格的关键是把连续色调离散为平面三角形。流程是先对灰度图做 Canny 边缘检测从边缘像素中采样出关键点再用这些点构造 Delaunay 三角网把图片分割成一系列相互邻接且互不重叠的三角形区域最后对每个三角形填充单一颜色。Delaunay 三角剖分要解决三角形形态问题每个三角形的外接圆内部不包含其他点这样生成的三角形外轮廓饱满不会出现大量狭长劣质形状。MATLAB 里直接调用 delaunay(px, py) 就能完成剖分复杂度在 O(NlogN) 量级真正耗时的是后续对每个三角形做像素级掩码与上色。采样点数越少三角形越大画面越抽象采样点越多越贴近原图但风格化特征也随之减弱。实际代码里要明确区分边缘点和采样点这两个集合采样阶段是控制风格强度的阀门。2.4 三个算法的复杂度与实现代价对比风格核心算子主要参数计算热点彩铅Canny、高斯扩散、灰度量化strokeSigma、toneLevels边缘检测与高斯模糊油画滑动窗口亮度分箱、均值染色winSize、binNum逐像素邻域统计低多边形Canny、Delaunay、poly2mask 填充sampleNum多边形栅格化与上色三种风格在 MATLAB 下的开销并不对等。彩铅风格基本是全局矩阵运算复杂度接近 O(rows×cols)毫秒级出图。油画每一像素都做邻域统计复杂度约为 O(rows×cols×winSize²)是三者中最容易拖慢程序的部分。低多边形的剖分本身很快但 poly2mask 对每个三角形生成逻辑掩码是像素级操作三角形数量到万级时耗时明显。项目时间规划里先分别完成三种算法再优化整合的顺序正好对应这条复杂度分析先跑通再谈优化。3. MATLAB 实现彩铅风格边缘扩散与色调量化融合3.1 读图、灰度化与函数骨架开始写算法前先把输入统一成 double 类型的灰度图。rgb2gray 对 uint8 输入返回 0~255 区间直接参与乘法容易丢精度转成 double 后整体归一化到 0~1后续矩阵运算的系数才有直观含义。若原图来自手机拍摄或 JPEG 压缩建议先做一次轻度高斯去噪避免噪声被 Canny 当成假边缘。function out color_pencil_effect(img, strokeSigma, toneLevels) % 彩铅风格化主函数 % 输入: % img : uint8 或 double 的 RGB 图像 % strokeSigma : 笔画高斯扩散标准差, 通常 1.0~2.5 % toneLevels : 色调量化级数, 通常 3~12 % 输出: % out : 处理后的 RGB 图像(double, 0~1) img im2double(img); if size(img, 3) 3 gray rgb2gray(img); else gray img; end gray imgaussfilt(gray, 0.8); % 轻度去噪 % 1) 笔画结构图 edges edge(gray, canny, [0.08 0.2]); stroke imgaussfilt(double(edges), strokeSigma); % 2) 色调渲染图 quant 1 / toneLevels; tone floor(gray / quant 0.5) * quant; % 分段量化 tone 0.3 0.65 * tone; % 压缩亮度范围, 留白给笔画 % 3) 融合 pencilGray tone .* (1 - 0.75 * stroke); % 4) 转彩色: 保留原图色相与饱和度, 替换明度 if size(img, 3) 3 hsv rgb2hsv(img); hsv(:, :, 3) pencilGray; out hsv2rgb(hsv); else out pencilGray; end if nargout 0 imshow(out); end end3.2 笔画结构生成与 Canny 阈值调整代码中 edge(gray, canny, [0.08 0.2]) 的双阈值需要根据图片内容调整。下限决定哪些弱梯度算边缘低于 0.08 的局部变化全部被忽略上限决定强边缘的确认标准高于 0.2 的梯度必定成为边缘。上下限之间的梯度像素根据连通性决定归属。对城市建筑这类直线多的图建议把下限抬到 0.12 防止线条过碎对树木、毛发这类纹理复杂的图降到 0.05 能保留更多结构。strokeSigma 控制线条的物理宽度它作用在二值边缘上做高斯扩散值越大线条越粗、越柔和通常 1.0~2.5 之内变化就已经非常明显。3.3 色调量化与亮度压缩的作用quant 1/toneLevels 把连续亮度切成离散层级。toneLevels 取 4 或 5 时天空、墙面被压缩成两三个亮度层级手绘感很强取到 10 以上则色调层次接近原图此时更像浅色线稿叠在原图上。再加上 0.30.65tone 这层压缩让最终亮度区间落在 0.3~0.95 而不是铺满 0~1目的是给后续笔画压暗留空间。这个系数如果改成 0.20.8tone暗部会更重整体对比更强。3.4 参数参考与快速调优参数推荐范围调参方向strokeSigma1.0~2.5越大线条越粗越柔toneLevels3~12越小色块越重手绘感越强Canny 下限0.05~0.12越低边缘越多笔画越密压暗系数0.5~0.9越大线条越黑对比越强实际调试时建议把函数封装后写一段循环在同一张图上扫描 toneLevels 的 6 个取值并排输出先锁定色调层级再单独调 strokeSigma。这个顺序能避免两个参数同时在变导致的主观误判。4. MATLAB 实现油画风格亮度众数与邻域统计4.1 为什么染色要用分箱后的均值重申一下核心逻辑对每个像素找邻域内出现次数最多的亮度分箱然后对该分箱内所有像素的 R、G、B 分别求平均。这里有一个容易忽略的细节众数分箱可能包含多个亮度值直接取该分箱的索引作为输出会丢失原色必须回到原图去取这些像素的色彩均值。这也是油画效果能保留适当色彩饱和度而不是变成灰度图的原因。4.2 基础循环实现与关键代码function out oil_paint_effect(img, winSize, binNum) % 油画风格化主函数 % 输入: % img : uint8 或 double 的 RGB 图像 % winSize : 邻域窗口边长, 奇数, 建议 5~15 % binNum : 亮度分箱数, 建议 8~16 % 输出: % out : 处理后 RGB 图像 img im2double(img); [rows, cols, ~] size(img); gray im2double(rgb2gray(img)); % 亮度分箱: 把连续亮度映射到 1~binNum 的整数编号 idxMap min(floor(gray * binNum) 1, binNum); pad floor(winSize / 2); out zeros(size(img)); for r 1:rows for c 1:cols % 邻域截断方式取块, 边缘窗口会变小 r1 max(r - pad, 1); r2 min(r pad, rows); c1 max(c - pad, 1); c2 min(c pad, cols); patchIdx idxMap(r1:r2, c1:c2); patchR img(r1:r2, c1:c2, 1); patchG img(r1:r2, c1:c2, 2); patchB img(r1:r2, c1:c2, 3); % 统计亮度分箱出现次数 counts zeros(1, binNum); for k 1:binNum counts(k) sum(patchIdx(:) k); end [~, bestK] max(counts); % 分箱内所有像素的 RGB 均值 mask (patchIdx bestK); out(r, c, 1) mean(patchR(mask)); out(r, c, 2) mean(patchG(mask)); out(r, c, 3) mean(patchB(mask)); end end if nargout 0 imshow(out); end end注意 idxMap 的映射公式gray 为 1.0 时 floor(1*binNum)1 会得到 binNum1必须用 min 压回 binNum。这个边界条件不处理最后一级亮度分箱会永远选不到。分箱编号本身不参与输出颜色计算只作为聚类分组依据所以它对噪声有天然的容忍度这也正是油画效果与直接阈值分割的区别。4.3 边界填充与加速优化上面代码在图像边缘处窗口自动截断导致四边邻域变小统计结果和内部不一致视觉上会出现一圈窄边。常见做法是先用 padarray 做边界复制填充再做循环。改进版如下% 边界复制填充, 保证每个像素的邻域都是 winSize*winSize imgPad padarray(img, [pad pad], replicate); idxPad padarray(idxMap, [pad pad], replicate); for r 1:rows for c 1:cols patchIdx idxPad(r:r2*pad, c:c2*pad); % 取出 patchR/patchG/patchB 后, 统计逻辑与上面相同 end end进一步优化可以把内层 counts 循环替换成 accumarray 一次完成。代码里最耗时的部分是双重 for 循环内的矩阵切片而不是统计本身。如果运行环境允许还可以用 parfor 把最外层循环并行化但要注意 out 矩阵按行写入时的内存竞争。很多初学者误以为把 winSize 调大就能无限增强油画感实际上超过 15 后画面只剩大色块纹理细节全部丢失此时应该回头调 binNum 而不是继续加窗口。4.4 和形态学处理的边界划分经常有人用 imdilate、imerode 或 imopen 组合出类似效果但这类形态学操作按结构元素决定像素取值不统计颜色出现频率得到的是浮雕或磨皮质感。油画效果的特殊之处在于刻意放大邻域内的多数派颜色让原本过渡平滑的区域被少数几个主色取代这种颜色选举逻辑只有众数滤波能直接表达。5. 低多边形风格的 MATLAB 实现与关键点抽取5.1 边缘检测与采样策略低多边形的前置步骤是拿到足够的轮廓点。先用 Canny 提取边缘再用 find 取得全部边缘像素坐标。一张 100 万像素的图通常有数万个边缘点直接全部送入 delaunay 会生成几十万三角形运行时间完全不可控。常见做法是限制采样数从边缘点中按均匀分布抽取一部分再把画面四角补进去防止边界处出现跨全图的狭长三角形。% 低多边形前置: 边缘检测与关键点采样 gray im2double(rgb2gray(img)); edgeImg edge(gray, canny, [0.1 0.25]); [yPts, xPts] find(edgeImg); sampleNum 1500; idx randperm(numel(xPts), min(sampleNum, numel(xPts))); pts [xPts(idx), yPts(idx)]; % 补上四个角点, 避免画布边缘被大三角形侵占 [rows, cols, ~] size(img); pts [pts; 1, 1; cols, 1; 1, rows; cols, rows];随机采样的问题在于纹理复杂的区域点会高度聚集平滑区域反而没有点。更均衡的方式是把画布切成网格每个网格最多保留一个边缘点。我一般先用 unique 去除重复坐标再基于空间网格抽稀这样三角形大小分布更均匀画面更接近真正的 Low Poly 审美。采样点数从 800 到 3000 之间变化时风格强度跨度很大3000 以上基本看不出风格化痕迹。5.2 Delaunay 剖分与三角形索引MATLAB 的 delaunay 函数输入两组坐标向量返回 m×3 的连接矩阵每行是三角形三个顶点在 pts 中的行号。剖分结果不依赖图像分辨率只依赖点数所以低多边形算法中图像尺寸对性能的影响远小于三角形数量。始终记住 pts 的行号从 1 开始和边缘检测坐标直接对应即可。tri delaunay(pts(:,1), pts(:,2)); % tri 的每一行形如 [i j k], 表示 pts(i), pts(j), pts(k) 构成一个三角形Delaunay 剖分天然会跳过共线点因此无需对 pts 做额外去重除非点数极大导致内存压力。这个阶段还可以用 delaunayTriangulation 类替代函数式调用它能额外提供邻接信息便于后续做三角形合并或网格简化但在本项目中基本用不到。5.3 三角形上色与输出上色阶段有两种路线。用 poly2mask 生成像素级掩码适合继续做图像处理用 patch 在 figure 中直接绘制存储量小但输出不是标准图像矩阵。工程上推荐 poly2mask 方案因为它能无缝接回 imshow 和 imwrite 流程。function out low_poly_effect(img, sampleNum) % 低多边形风格化主函数 % 输入: % img : uint8 或 double 的 RGB 图像 % sampleNum : 边缘点采样数, 建议 800~3000 % 输出: % out : 处理后 RGB 图像 img im2double(img); [rows, cols, ~] size(img); gray im2double(rgb2gray(img)); % 1. 边缘检测与采样(代码同 5.1) edgeImg edge(gray, canny, [0.1 0.25]); [yPts, xPts] find(edgeImg); idx randperm(numel(xPts), min(sampleNum, numel(xPts))); pts [xPts(idx), yPts(idx)]; pts [pts; 1, 1; cols, 1; 1, rows; cols, rows]; % 2. Delaunay 三角剖分 tri delaunay(pts(:,1), pts(:,2)); % 3. 逐三角形填充 out zeros(rows, cols, 3); for t 1:size(tri, 1) v1 pts(tri(t,1), :); v2 pts(tri(t,2), :); v3 pts(tri(t,3), :); % 取三角形质心处的像素颜色作为代表色 cx round((v1(1) v2(1) v3(1)) / 3); cy round((v1(2) v2(2) v3(2)) / 3); cx max(1, min(cols, cx)); cy max(1, min(rows, cy)); mask poly2mask([v1(1) v2(1) v3(1)], ... [v1(2) v2(2) v3(2)], rows, cols); for ch 1:3 channel out(:, :, ch); channel(mask) img(cy, cx, ch); out(:, :, ch) channel; end end if nargout 0 imshow(out); end end上色时使用质心采样而不是三角形内所有像素的均值是为了避免循环内再做一次大矩阵求均值。质心采样在三角形较大时可能丢掉内部纹理信息但对低多边形风格而言这反而强化了色块扁平化效果。如果想要更精细的结果可以把代表色改为三角形三顶点颜色平均耗时增量不大但过渡会更柔和。5.4 三种风格的输出特征与整合建议彩铅输出保留较多空间细节视觉效果来自笔画叠加油画输出块面感最强适合人像和风景低多边形输出几何感明确最适合建筑与海报类题材。三个函数最终的输出接口完全一致都是 double 类型的 RGB 矩阵这为 GUI 整合创造了条件。在开始整合之前建议给三个函数各自加一个参数结构体把核心参数集中管理避免 GUI 回调函数里传参混乱。6. 风格化 GUI 的参数调试与验证技巧6.1 从函数到界面三根滑杆调出完整效果在 App Designer 中建立一个简单界面一个坐标区显示原图一个坐标区显示结果再加三个滑杆分别对应当前风格的参数。核心是回调里的参数读取与耗时记录。以下是 App Designer 风格的回调片段% 油画窗口大小滑杆的回调 function WinSizeSliderValueChanged(app, ~) app.winSize round(app.WinSizeSlider.Value); tic; app.result oil_paint_effect(app.origImg, app.winSize, app.binNum); elapsed toc; imshow(app.result, Parent, app.ResultAxes); app.StatusLabel.Text sprintf(渲染耗时 %.2f 秒, elapsed); end这三个风格的核心参数对应关系如下风格滑杆参数建议调节范围先固定再动哪个彩铅strokeSigma0.5~3.0先固定 toneLevels8彩铅toneLevels3~12再调 strokeSigma油画winSize3~15先固定 binNum12油画binNum8~20再调 winSize低多边形sampleNum300~3000先固定 Canny 阈值调试时要避免两个参数同时乱动。我习惯先把一个参数固定在一个视觉合理的中间值遍历另一个参数的六个取值拼成一张对比图。用 subplot 或 tiledlayout 输出一张 2×3 网格图比在 GUI 里来回拖动滑杆更有效率。6.2 用 ValueChangingFcn 降低拖动卡顿滑杆拖动过程中会连续触发回调油画风格一次渲染可能耗时几百毫秒到数秒直接卡界面。解决方法是把渲染拆成两段拖动过程中使用 ValueChangingFcn 处理实时预览只更新参数文本不重算图像鼠标松开后由 ValueChangedFcn 触发真正的渲染。对应代码如下% 拖动中只更新显示, 不渲染 function WinSizeSliderValueChanging(app, ~) value round(app.WinSizeSlider.Value); app.WinSizeLabel.Text sprintf(窗口大小: %d, value); end % 松开后才执行油画渲染 function WinSizeSliderValueChanged(app, ~) app.winSize round(app.WinSizeSlider.Value); tic; app.result oil_paint_effect(app.origImg, app.winSize, app.binNum); elapsed toc; imshow(app.result, Parent, app.ResultAxes); app.StatusLabel.Text sprintf(渲染耗时 %.2f 秒, elapsed); end这个改动能明显减少拖动过程中的卡顿且不会丢失最终效果。滑块参数为整数时使用 round 取整可以避免 winSize 出现偶数维持窗口中心像素对称。完成这些回调之后把三个函数、一个打开图片按钮和三个滑杆组合起来就是一个完整可交互的风格化工具。参数与效果之间的对应关系通过逐项扫描和耗时打印验证一遍比凭感觉拖动滑杆更容易找到每个风格的视觉甜点区。本文还有配套的精品资源点击获取
返回列表