简介:本资源是一份面向数字图像处理初学者与Matlab实践者的图像栅格化处理入门级实现方案,聚焦将矢量图形或连续图像数据转化为像素矩阵这一核心任务,适用于课程设计、实验教学及算法原理验证场景。压缩包共2个文件(1个MATLAB源码脚本、1张示例测试图像),体积仅9KB,轻量简洁,便于快速部署与调试;其中.m文件封装了图像读取、矩阵采样、量化映射及显示保存等完整流程,jpg图像用于直接运行验证效果。已有103人学习下载,反映出其在基础图像处理教学中的实用价值。读者可直接复现栅格化全过程,深入理解采样率设置、像素重映射、颜色空间转换等关键概念,并基于该脚本拓展旋转缩放、直方图均衡等进阶操作,代码结构清晰、注释完备,具备良好可读性与二次开发基础。 如果你在MATLAB里搜“栅格化”,大概率会翻到三种完全不同的解释:有人要做矢量图转位图,有人要做散点数据的网格化插值,还有人其实只是想给照片做出马赛克像素风。这三件事在MATLAB里都能和“rasterization”沾上边,但代码思路完全是两码事。这篇文章要讲的,是其中需求最集中、也最常被当成“图像处理大作业”的那一类——把一张连续色调的图像,按照规则网格划分后,重建成由一个个色块构成的栅格效果。我会从最基础的原理讲起,给出可以直接拿去用的Matlab源码,再把块大小、采样策略、边界处理、性能优化这些细节一次说透。不管你是要交作业、做像素海报、给图片局部打码,还是单纯想搞明白“imresize两步法”为什么能秒杀三重循环,这篇都适合你。
1. 栅格化处理前,先分清你到底是哪一种需求
1.1 三种常见的“栅格化”方向
先说个我自己的感受:刚接触这个需求的人,很容易在搜索阶段就被带偏。因为“栅格化”这个词在不同领域里的含义差别实在太大。
第一种是矢量图形栅格化。比如你用MATLAB画了个figure曲线图,里面的线条、文字本质上是矢量对象,保存成png时MATLAB内部会做一次栅格化渲染,把矢量描述转换为像素点阵。这个方向在MATLAB里通常一个print命令就解决了,和图像处理作业关系不大。
第二种是离散点数据网格化,常见于测绘、气象、有限元仿真。手头有一堆散乱的采样点坐标和数值,想插值成规则的网格数据,用scatteredInterpolant配合meshgrid来做。这是“数据栅格化”,不是“图像栅格化”。
第三种才是大众语境里常说的图像栅格化/像素化:一张正常分辨率的照片,按方块切割,每个方块区域用一种颜色填充,形成类似马赛克或者像素艺术的效果。标题里的“图像栅格化处理(源码)”指向的正是这一类。
| 方向 | 输入 | 输出 | 核心函数 |
|---|---|---|---|
| 矢量转栅格 | figure/矢量图 | 位图 | print、exportgraphics |
| 离散点网格化 | 散点坐标+数值 | 规则网格 | scatteredInterpolant |
| 图像分块栅格化 | 像素矩阵 | 色块化图像 | 自定义分块+均值/采样 |
做这个项目前,我建议你先花两分钟确认一下自己的真实需求,方向搞错了,后面的代码全部白写。
1.2 图像栅格化的输出到底是什么
从严格意义上说,任何数字图像本身就是栅格数据,每个像素就是栅格单元。所谓“栅格化处理”,本质上是降低空间分辨率的可视化表达:把原来的M×N个像素点,重组成(M/k)×(N/k)个“超级像素”,每个超级像素占据k×k的像素区域,内部颜色统一。
举个例子:一张512×512的图片,如果块大小k=8,那么处理后相当于变成了64×64个色块,每个色块内部所有像素颜色相同。你看到的图片还是512×512,但信息量已经降到了原来的六十四分之一。这就是栅格化最核心的直觉——用空间分辨率换视觉风格,或者掩盖细节。
1.3 为什么MATLAB适合做这件事
选MATLAB不是因为它是最快的图像处理工具,而是因为它对图像的抽象方式非常友好。图像在MATLab里就是一个多维数组,灰度图是二维矩阵,彩色图是三维矩阵,所有图像处理操作都可以直接转换成矩阵运算。这意味着“分块”“取均值”“像素赋值”这些栅格化步骤,翻译成代码的路径特别短,非常适合理解算法本质、快速验证思路。
另外,MATLAB自带imshow做可视化、imwrite做导出,图像处理大作业通常还会要求对比处理前后的效果,这套流程用MATLAB写起来比其他语言方便很多。下面的实现我会尽量不依赖图像处理工具箱之外的东西,保证可移植性。
2. 核心算法拆解:分块平均和中心采样到底差在哪
2.1 图像就是一张数字矩阵
要写栅格化代码,脑子里必须时刻有矩阵意识。灰度图其实就是h×w的矩阵,每个元素是0~255的整数,彩色图则是h×w×3的三维矩阵,三个通道分别代表红、绿、蓝。
栅格化处理的基本单位是块(block)。你选定一个块的边长k,然后把整个图像按k×k划分。常规做法是非重叠分块:从左上角开始,每隔k个像素取一块,块与块之间互不重叠。如果你想要更加艺术化的效果,也可以做重叠分块或者偏移分块,但最基础、最稳定的还是非重叠。
2.2 分块平均法
分块平均是最符合直觉的方法:对每个k×k区域内的所有像素求平均,得到一个新的颜色值,再把整个区域的所有像素都赋成这个平均值。
数学上就是:
输出块内像素值 = round( block内所有像素的平均值 )颜色统一之后,块的边界变得清晰,整体画面呈现出“被压缩过”的矢量拼贴感。这个方法的优点是抗噪、稳定,即使原图某个区域内有多余的纹理细节或噪点,取均值后也会被抹平,视觉上非常干净。缺点是计算量略大,需要对每个块做一次遍历求和,但在MATLAB里用mean(block(:))一行就能搞定,性能压力很小。
2.3 中心采样法
中心采样则是另一个极端,每个块不计算均值,而是直接取块的中心像素值,或者取块内左上角第一个像素值,然后用这个值填满整块。
blockValue = block(ceil(end/2), ceil(end/2)); % 中心像素这种方法速度快,代码也更简单,但效果上会保留更多的原图高频信息。如果原图某个块内部颜色变化剧烈,只取中心点可能会让结果看起来比平均法更“跳跃”,甚至产生一种粗糙的半调感。不过在某些场景下,这种“不平均”反而更有味道,比如做复古像素风头像,中心采样能保留更多轮廓边缘。
2.4 两种方法的对比与选择
| 方法 | 视觉特点 | 计算逻辑 | 适用场景 |
|---|---|---|---|
| 分块平均 | 平滑、干净、色块过渡自然 | 块内所有像素参与运算 | 像素艺术、隐私打码、缩略图预览 |
| 中心采样 | 细节保留多、边缘较硬 | 只取块内一个像素 | 快速预览、风格化粗糙感、低延迟处理 |
我的建议是:主方案用分块平均,因为它最稳定、效果最可控。中心采样可以作为一种可选模式留在代码里,方便对比。很多MATLAB大作业的评分重点其实是对比图,能展示两种方法的差异,反而是加分项。
3. 可直接运行的Matlab源码与逐行说明
3.1 灰度图栅格化完整实现
先写灰度图版本,逻辑最清晰,适合理解骨架。
function raster = rasterizeGray(img, blockSize) % rasterizeGray 灰度图像栅格化 % img: 灰度图矩阵 (h x w),uint8类型 % blockSize: 正方形块的边长 % raster: 栅格化输出,尺寸与img一致 [h, w] = size(img); raster = zeros(h, w, 'uint8'); % 预分配输出 for i = 1:blockSize:h iEnd = min(i + blockSize - 1, h); % 处理边缘不整除 for j = 1:blockSize:w jEnd = min(j + blockSize - 1, w); block = img(i:iEnd, j:jEnd); blockAvg = mean(block(:)); % 块内所有像素均值 blockValue = uint8(round(blockAvg)); % 四舍五入并转uint8 raster(i:iEnd, j:jEnd) = blockValue; end end end调用示例:
img = imread('cameraman.tif'); % 经典灰度测试图 raster = rasterizeGray(img, 8); imshowpair(img, raster, 'montage'); title('灰度图栅格化,块大小=8');这里有两个细节新手容易漏。第一,mean返回的是double类型,直接赋给uint8矩阵会产生警告甚至截断错误,必须先round再uint8。第二,边缘处理非常关键,如果图像高度h=100、块大小k=6,最后一次循环走到i=97,iEnd就必须截到100,否则下标越界,min函数就是干这个的。
3.2 彩色图栅格化完整实现
彩色图要处理的就不是二维矩阵了,而是三维数组。分块时同样按空间位置切,但每个块内部包含三个通道。
function raster = rasterizeColor(img, blockSize) % rasterizeColor 彩色图像栅格化 % img: RGB图像矩阵 (h x w x 3),uint8类型 % blockSize: 正方形块的边长 [h, w, ~] = size(img); raster = zeros(size(img), 'uint8'); for i = 1:blockSize:h iEnd = min(i + blockSize - 1, h); for j = 1:blockSize:w jEnd = min(j + blockSize - 1, w); block = img(i:iEnd, j:jEnd, :); % h' x w' x 3 blockAvg = mean(mean(double(block), 1), 2); % 1x1x3 double blockValue = uint8(round(blockAvg)); % 1x1x3 uint8 % 复制到整个块区域 blockH = iEnd - i + 1; blockW = jEnd - j + 1; raster(i:iEnd, j:jEnd, :) = repmat(blockValue, blockH, blockW, 1); end end end调用示例:
img = imread('peppers.png'); raster = rasterizeColor(img, 12); imshow(raster); title('彩色图栅格化,块大小=12');这里我用了mean(mean(double(block), 1), 2),目的是让三个通道分别求均值,而不是把所有通道混在一起求一个灰度值。如果直接mean(block(:)),彩色图会被压成灰色,这是彩色版本最常见的错误。关于repmat,是因为blockValue是1×1×3的形状,直接给i:iEnd, j:jEnd, :赋值时,有些MATLAB版本不会自动做尺寸扩展,用repmat显式复制成块区域大小,最保险。
3.3 极简高效方案:imresize两步法
如果你只是想要栅格化效果,不太在乎手写循环的过程乐趣,那我强烈推荐用imresize两步完成。我第一次发现这个方法时,有种“原来代码可以这么省”的顿悟感。
img = imread('peppers.png'); [h, w, ~] = size(img); blockSize = 12; smallH = max(1, round(h / blockSize)); smallW = max(1, round(w / blockSize)); % 第一步:缩小,相当于每块取一个代表色 small = imresize(img, [smallH smallW], 'bilinear'); % 第二步:最近邻放大,让色块铺满原尺寸 raster = imresize(small, [h w], 'nearest');原理很简单:先用双线性插值把图缩到(h/k)×(w/k),这时候每个目标像素其实已经包含了原图一个块的信息;再用'nearest'把它放大回原尺寸,色块边界清晰,颜色整齐,效果接近分块平均法,但速度比双重循环快一个数量级。
我用一张1024×768的彩色图实测过:循环法大概要跑1到2秒,blockproc要0.3秒左右,imresize两步法则基本在0.05秒以内。做批量处理,比如一次处理几十张图,速度优势会非常明显。要注意的是,如果图像尺寸不能整除块大小,缩小后放大回来的最右侧或最底部的块宽高会略有偏差,但不影响整体观感。
4. 栅格效果的进阶玩法:从方块到可调质感
4.1 块大小的选择逻辑
块大小k是整个栅格化效果里最敏感的调参项,没有统一标准,但有几个经验值可以参考。
k=2~3:几乎看不出变化,适合做轻度平滑。k=5~10:能明显看到像素块,整体还保留原图结构,适合大作业展示“栅格化”效果。k=15~30:色块非常清晰,细节大幅丢失,适合做像素风格的创意图。k>50:抽象化,基本只能辨认大色块轮廓,适合做装饰海报。
我的建议是,把blockSize写成函数参数,不要硬编码。这样你可以写一个简单的循环,生成不同块大小的效果对比图,交作业时一目了然:
img = imread('peppers.png'); for k = [4 8 16 32] subplot(2, 2, find([4 8 16 32] == k)); imshow(rasterizeColor(img, k)); title(['块大小 = ' num2str(k)]); end4.2 给色块加网格线
有时候光有颜色块还不够,加上分隔网格线之后,那种“栅格感”会瞬间增强,看起来更像图纸或者工艺拼贴。
最简单的方法,是在栅格化输出矩阵上把分界线位置的像素置为黑色:
raster = rasterizeColor(img, 16); raster(:, 1:16:end, :) = 0; raster(1:16:end, :, :) = 0;这两行代码会把每隔16个像素的整行和整列全部变成黑色。如果你不想要黑色,想用白色或者半透明感,可以换成255,效果完全不同。实测下来,黑色网格线的栅格图更适合做“数据可视化”风格的展示,白色网格线则更像手工像素画。
4.3 局部栅格化:只模糊指定区域
应用上很常见的一个需求是隐私保护,比如给照片里的人脸区域打码。如果整张图栅格化,背景细节也会丢失,观感不好。这种时候可以只对局部区域做处理。
思路是:先提取ROI(感兴趣区域),对ROI单独栅格化,再贴回原图对应位置。
img = imread('peppers.png'); roi = img(60:180, 100:220, :); % 提取目标区域 roiRaster = rasterizeColor(roi, 10); % 局部栅格化 img(60:180, 100:220, :) = roiRaster; % 覆盖回原图 imshow(img);这种方式在简历作品集里很加分,因为它展示了你对图像处理的综合控制能力,不只是粘贴一个全图滤镜。你也可以事先定义一个矩形区域数组,循环处理多个人脸。
4.4 栅格化后的颜色量化:像素风的关键一步
如果你追求更“像素游戏”风格,光分块还不够,还需要对颜色做量化,把大量相近色压缩成少数几个调色板颜色。MATLAB的rgb2ind可以直接做这件事。
[X, map] = rgb2ind(raster, 8, 'nodither'); % 每个块只能从8种颜色中选择 rasterQuant = ind2rgb(X, map); imshow(rasterQuant);X是索引图像,一个整数矩阵,每个整数对应调色板map里的一个RGB颜色。'nodither'表示不做颜色抖动,色块内部会非常干净。颜色数量8可以改成4、16或者32,数值越小,风格越硬朗。
把“栅格化(空间量化)”和“颜色量化”两个步骤结合,就能做出非常接近游戏素材的像素风格图。这也是我在这类项目里最喜欢玩的一个点——同样的算法,加上不同调色板,就能朝复古、文艺、赛博几个方向走。
5. 应用场景与实测避坑记录:从大作业到像素海报
5.1 三类最常见的落地场景
一是像素艺术创作。把照片变成像素头像、像素海报,配合颜色量化后效果更好。二是在隐私保护与脱敏里做局部马赛克,重点是只处理小范围区域,不破坏整体画面。三是图像压缩预览,生成缩略图时先用小尺寸栅格图代替原始大图,降低加载压力。这个思路在做批量图片管理工具时很好用,用户先看到栅格预览图,点击后才加载原图,整体体验会顺滑很多。
5.2 边界处理:不整除时最容易翻车
图像尺寸能被块大小整除是少数情况,绝大多数时候总会在右边界和下边界留下一个“不完整块”。很多初学者第一次写循环,会直接用i: i+blockSize-1切片,遇到512×512和k=30这类组合,最后一次循环必然会数组越界。
解决办法就是前面代码里的min(i + blockSize - 1, h)。这个方法既不会越界,也能保证最后一排、最后一列虽然块不完整,但依然输出正常。如果你追求完美,希望所有块都是完整正方形,可以在imread之后先做padarray填边:
hPad = blockSize - mod(h, blockSize); wPad = blockSize - mod(w, blockSize); imgPadded = padarray(img, [hPad wPad], 'replicate', 'post');'replicate'表示用边缘像素值重复填充,这样处理后图像尺寸就是块大小的整数倍,栅格化完再裁回原尺寸即可。不过实际效果没那么讲究,直接用min截断处理也很自然,不用刻意补边。
5.3 uint8精度和数据类型坑
这个坑我必须单独提,因为太容易踩了。假设一个块内的像素值全是250和240,均值是245.4,直接uint8(245.4)在MATLAB里会四舍五入成245,没问题。但如果某些块的均值是244.7,直接转uint8也对应245。麻烦的是如果不做round,把mean的结果直接塞进uint8矩阵,轻则出现精度损失,重则报错,因为double到uint8的隐式转换在旧版本MATLAB里直接不允许。
彩色图版本更要注意,mean三个通道算出来的值可能是小数,要全部round之后一起转uint8。我在实际测试中发现,先round再uint8比先uint8再取整要稳定得多,因为后者的四舍五入行为有时候不符合直觉。
5.4 blockproc函数看起来美,用起来要小心
blockproc是MATLAB图像处理工具箱里专门做分块操作的函数,表面上看非常适合栅格化。但它的回调函数必须返回和输入块相同尺寸的数据,否则报错。而且默认情况下,blockproc对灰度图没问题,彩色图要处理三通道,回调函数书写稍微复杂一点。
fun = @(block) uint8(round(mean(block.data, [1 2]))) .* ones(size(block.data), 'uint8'); raster = blockproc(img, [blockSize blockSize], fun);mean(block.data, [1 2])表示对前两个维度求平均,得到1×1×3的结果,再乘一个全1的块矩阵,就得到整个块赋值的彩色图。这个写法在R2018b之后可以用,旧版本需要改成嵌套mean(mean(block.data,1),2)。
但从性能角度,blockproc并没有想象中快,尤其是块很小的时候,函数调用开销反而比手写循环大。所以我的建议是:理解原理用循环,追求简单用imresize,blockproc只在你需要额外块内统计信息时再用。
5.5 性能对比与批量处理建议
我把512×512灰度图、1024×768彩色图分别跑了一遍,不同方法的时间差异非常明显:
| 方法 | 代码复杂度 | 实测速度感受 | 场景建议 |
|---|---|---|---|
| 双重循环 | 低 | 慢,但可接受 | 学习原理、大作业展示 |
| blockproc | 中 | 中等 | 需要块内统计时使用 |
| imresize两步 | 极低 | 最快 | 批量处理、工程落地 |
如果你的项目需要批量处理一堆图片,我建议不要每次都imread、栅格化、imshow,而是封装成一个函数,加上导出功能,直接写到一个输出目录。
function batchRasterize(inputDir, outputDir, blockSize) files = dir(fullfile(inputDir, '*.jpg')); for i = 1:numel(files) img = imread(fullfile(inputDir, files(i).name)); raster = rasterizeColor(img, blockSize); imwrite(raster, fullfile(outputDir, [files(i).name(1:end-4) '_raster.png'])); end end导出用imwrite,格式选png,因为png是无损压缩,栅格化后的色块边缘不会因为jpg压缩而产生多余伪影。这点是我做过几批图之后才注意到的,用jpg保存的栅格图,块边界容易出现杂色,做像素风作品时非常影响质感。
做完整套代码和实验后,我最大的感受是:栅格化这个题目,代码量不大,但涉及的概念密度很高——矩阵思维、插值、量化、边界处理、性能取舍全都能串起来。尤其是把“分块平均”和“imresize两步法”放在一起看,你会发现很多看似高级的图像特效,底层无非是采样和重建这两件事。如果你想在这个基础上继续深入,下一步可以试试把栅格化和边缘检测结合,先检测出物体轮廓,再对轮廓内外使用不同块大小的非均匀栅格化,效果会比全图统一块大小更有层次感。
本文还有配套的精品资源,点击获取