Rust图像处理第7节-马赛克像素化:分块取平均色实现打码风格

0 阅读5分钟

🦀 Rust + WASM 实战系列 第 7 篇 阅读时间:约 5 分钟 | 实战可运行

📌 写在前面

模糊是把像素"柔化",马赛克是把像素"涂掉"——同一块内全变成一个颜色

原理极简:分块 → 算块平均色 → 整块涂成平均色。3 个步骤搞定。


🚀 TL;DR

原图        分块 (10×10)       每块取平均色          输出
██████      ██ ██ ██ ██        整个块都涂同一个色    像素化
██████  →   ██ ██ ██ ██   →   (取 100 像素均值)  →  ████
██████      ██ ██ ██ ██                            ████
██  ██      ██ ██ ██ ██                            ████
block_size视觉效果
5轻微像素化
10标准马赛克 ⭐
20抽象艺术
50色块拼贴

📖 目录

  1. 马赛克 vs 模糊:本质区别
  2. 核心算法:3 步搞定
  3. 关键代码
  4. 前端效果展示
  5. 边界处理
  6. 进阶:彩色 vs 灰度马赛克
  7. 应用场景
  8. 参考资料

一、马赛克 vs 模糊:本质区别

维度模糊(任务 6)马赛克(任务 7)
粒度每个像素都重算一块像素共用一个值
信息保留平滑过渡,细节保留细节完全丢失
可逆性模糊后无法还原不可还原(信息已丢)
算法邻域平均 / 中值块内平均 → 整块填充
用途降噪、美化隐私打码、艺术化

马赛克 = "硬模糊",不仅柔化,直接涂掉


二、核心算法:3 步搞定

1. 分块:把图片按 block_size × block_size 切成网格
2. 算色:对每块的所有像素求平均 RGB
3. 填充:把整块都涂成这个平均色

栗子(block_size = 2)

原图 4×4(每块内故意画成 R/B 混合):

R R R B      ← 第 1 行
R B B B      ← 第 2B B B R      ← 第 3B B R R      ← 第 4

按 2×2 分块(4×4 / 2×2 = 2×2 = 4 块):

┌─────┬─────┐
│ R R │ R B │   块 13R, 1B)→ 平均 ≈ 橙红
│ R BB B │   块 22R, 2B)→ 平均 ≈ 紫
├─────┼─────┤
│ B BB R │   块 30R, 4B)→ 平均 ≈ 蓝
│ B B │ R R │   块 42R, 2B)→ 平均 ≈ 紫
└─────┴─────┘

输出(每块涂成自己的平均色,细节被"抹"成统一色):

┌─────┬─────┐
│≈橙红│ ≈紫 │
├─────┼─────┤
│ ≈蓝 │ ≈紫 │
└─────┴─────┘

这才是马赛克的真正效果——把每块的细节"抹"成统一色。


三、关键代码

// 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
            }
        }
    }
}

四、前端效果展示

d006f507-1fe8-49ee-bb3e-026c815a9a19.png

五、边界处理

图片大小通常不能被 block_size 整除

500 × 500 图 + block_size = 30
30 × 16 = 480  → 还剩 20 像素

解法:.min(h) 截断

let y_end = (by + bs).min(h);   // 不能超过图片高度
let x_end = (bx + bs).min(w);   // 不能超过图片宽度

效果:

完整块:30×30 = 900 像素
边缘小块:30 × 20 = 600 像素

所有块都正确处理,没越界。


六、进阶:彩色 vs 灰度马赛克

灰度马赛克

有时想要"老式电视打码"风格(人脸变马赛克但保持灰色调):

// 把整块算成灰度
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;

用途:新闻报道打码、保护隐私。

圆形马赛克

只对中心区域打码(保留边缘):

// 判断像素到中心的距离
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 之类:

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 #图像处理 #马赛克 #像素化 #打码 #算法