🦀 Rust + WASM 实战系列 第 7 篇 阅读时间:约 5 分钟 | 实战可运行
📌 写在前面
模糊是把像素"柔化",马赛克是把像素"涂掉" ------同一块内全变成一个颜色。
原理极简:分块 → 算块平均色 → 整块涂成平均色。3 个步骤搞定。
🚀 TL;DR
scss
原图 分块 (10×10) 每块取平均色 输出
██████ ██ ██ ██ ██ 整个块都涂同一个色 像素化
██████ → ██ ██ ██ ██ → (取 100 像素均值) → ████
██████ ██ ██ ██ ██ ████
██ ██ ██ ██ ██ ██ ████
block_size |
视觉效果 |
|---|---|
| 5 | 轻微像素化 |
| 10 | 标准马赛克 ⭐ |
| 20 | 抽象艺术 |
| 50 | 色块拼贴 |
📖 目录
- [马赛克 vs 模糊:本质区别](#马赛克 vs 模糊:本质区别 "#%E4%B8%80%E9%A9%AC%E8%B5%9B%E5%85%8B-vs-%E6%A8%A1%E7%B3%8A%E6%9C%AC%E8%B4%A8%E5%8C%BA%E5%88%AB")
- [核心算法:3 步搞定](#核心算法:3 步搞定 "#%E4%BA%8C%E6%A0%B8%E5%BF%83%E7%AE%97%E6%B3%953-%E6%AD%A5%E6%90%9E%E5%AE%9A")
- 关键代码
- 前端效果展示
- 边界处理
- [进阶:彩色 vs 灰度马赛克](#进阶:彩色 vs 灰度马赛克 "#%E5%85%AD%E8%BF%9B%E9%98%B6%E5%BD%A9%E8%89%B2-vs-%E7%81%B0%E5%BA%A6%E9%A9%AC%E8%B5%9B%E5%85%8B")
- 应用场景
- 参考资料
一、马赛克 vs 模糊:本质区别
| 维度 | 模糊(任务 6) | 马赛克(任务 7) |
|---|---|---|
| 粒度 | 每个像素都重算 | 一块像素共用一个值 |
| 信息保留 | 平滑过渡,细节保留 | 细节完全丢失 |
| 可逆性 | 模糊后无法还原 | 不可还原(信息已丢) |
| 算法 | 邻域平均 / 中值 | 块内平均 → 整块填充 |
| 用途 | 降噪、美化 | 隐私打码、艺术化 |
马赛克 = "硬模糊" ,不仅柔化,直接涂掉。
二、核心算法:3 步搞定
markdown
1. 分块:把图片按 block_size × block_size 切成网格
2. 算色:对每块的所有像素求平均 RGB
3. 填充:把整块都涂成这个平均色
栗子(block_size = 2)
原图 4×4(每块内故意画成 R/B 混合):
css
R R R B ← 第 1 行
R B B B ← 第 2 行
B B B R ← 第 3 行
B B R R ← 第 4 行
按 2×2 分块(4×4 / 2×2 = 2×2 = 4 块):
css
┌─────┬─────┐
│ R R │ R B │ 块 1(3R, 1B)→ 平均 ≈ 橙红
│ R B │ B B │ 块 2(2R, 2B)→ 平均 ≈ 紫
├─────┼─────┤
│ B B │ B R │ 块 3(0R, 4B)→ 平均 ≈ 蓝
│ B B │ R R │ 块 4(2R, 2B)→ 平均 ≈ 紫
└─────┴─────┘
输出(每块涂成自己的平均色,细节被"抹"成统一色):
┌─────┬─────┐
│≈橙红│ ≈紫 │
├─────┼─────┤
│ ≈蓝 │ ≈紫 │
└─────┴─────┘
这才是马赛克的真正效果------把每块的细节"抹"成统一色。
三、关键代码
rust
// 1. 双重循环:按 block_size 步长遍历"块起点"
for by in (0..h).step_by(block_size) {
for bx in (0..w).step_by(block_size) {
// 2. 算这一块的平均色
let mut sum = (0u32, 0u32, 0u32);
let mut count = 0u32;
let y_end = (by + block_size).min(h);
let x_end = (bx + block_size).min(w);
for y in by..y_end {
for x in bx..x_end {
let i = ((y * w + x) * 4) as usize;
sum.0 += pixels[i] as u32;
sum.1 += pixels[i + 1] as u32;
sum.2 += pixels[i + 2] as u32;
count += 1;
}
}
let avg = ((sum.0 / count) as u8,
(sum.1 / count) as u8,
(sum.2 / count) as u8);
// 3. 整块都涂成 avg
for y in by..y_end {
for x in bx..x_end {
let i = ((y * w + x) * 4) as usize;
result[i] = avg.0;
result[i + 1] = avg.1;
result[i + 2] = avg.2;
result[i + 3] = pixels[i + 3]; // 保留 alpha
}
}
}
}
四、前端效果展示
五、边界处理
图片大小通常不能被 block_size 整除:
ini
500 × 500 图 + block_size = 30
30 × 16 = 480 → 还剩 20 像素
解法:.min(h) 截断
rust
let y_end = (by + bs).min(h); // 不能超过图片高度
let x_end = (bx + bs).min(w); // 不能超过图片宽度
效果:
ini
完整块:30×30 = 900 像素
边缘小块:30 × 20 = 600 像素
所有块都正确处理,没越界。
六、进阶:彩色 vs 灰度马赛克
灰度马赛克
有时想要"老式电视打码"风格(人脸变马赛克但保持灰色调):
rust
// 把整块算成灰度
let gray = (0.2126 * avg.0 as f32
+ 0.7152 * avg.1 as f32
+ 0.0722 * avg.2 as f32) as u8;
result[i] = gray;
result[i + 1] = gray;
result[i + 2] = gray;
用途:新闻报道打码、保护隐私。
圆形马赛克
只对中心区域打码(保留边缘):
rust
// 判断像素到中心的距离
let dx = x as f32 - cx;
let dy = y as f32 - cy;
let dist = (dx * dx + dy * dy).sqrt();
if dist < radius {
// 在圆内,打码
} else {
// 圆外,原图
}
用途:证件照编辑、视频中的人脸打码。
像素艺术
block_size 调到图片宽度 / 32 之类:
rust
let block_size = (w / 32) as u32; // 32 块宽度的马赛克
效果类似 Minecraft 风格。
七、应用场景
| 场景 | 推荐 block_size |
|---|---|
| 隐私打码(人脸、车牌) | 10~20 |
| 艺术化(海报、封面) | 5~15 |
| 抽象化(背景) | 50~100 |
| 8-bit 复古游戏风 | w / 32 |
| 调试(看缩略图) | 视情况 |
跟模糊的选用
| 想要 | 用什么 |
|---|---|
| 柔化但看清细节 | 均值模糊(任务 6) |
| 完全打码 | 马赛克(任务 7) |
| 去除椒盐噪声 | 中值模糊(任务 6) |
| 艺术化 | 两者都行 |
八、参考资料
- Photoshop 文档:Filter → Pixelate → Mosaic
- GIMP 文档:Filters → Blur → Pixelize
- OpenCV 文档 :
cv2.blur()vs 自定义马赛克 - PIL/Pillow 库 :
Image.EFFECT.MOSAIC(早期版本)
🎁 写在最后
马赛克 = 最暴力的"打码" 。一行公式都没有,分块 + 平均 + 填充 3 步就完事。
比均值模糊更彻底 :模糊后还能看出人脸,马赛克后真·看不出来。
下篇预告:《暗角 & 复古胶片:四周衰减中心高亮 》------ 用距离做权重,离中心越远越暗,敬请期待。
📦 项目地址 :pixel-math-wasm 🦀 Rust + WebAssembly 实战系列
🏷️ 标签 :#Rust #WebAssembly #图像处理 #马赛克 #像素化 #打码 #算法
