马赛克效果的本质是区域像素均值替换:将图像划分为整数网格块(如10×10),每块内所有像素统一替换为该块BGR均值;需手动遍历ROI计算填充,避免resize插值导致的颜色偏移与边缘发虚。

马赛克效果的本质是区域像素均值替换
马赛克不是模糊,也不是降采样后放大,而是把图像划分成若干块(比如 10×10 像素),每一块内所有像素统一替换成该块的平均颜色。OpenCV 的 cv::resize 配合上采样能快速实现,但要注意:直接用 cv::resize 下采样再上采样会引入插值误差,导致颜色偏移;更稳妥的做法是手动遍历区块、计算均值、批量赋值。
常见错误是把“缩放后放大”当成马赛克——那样得到的是双线性插值结果,边缘发虚,且无法控制块大小粒度。真正的马赛克必须对齐整数网格,且每个块内部像素值完全一致。
用 OpenCV 手动实现块均值马赛克(C++)
核心逻辑:遍历每个马赛克单元左上角坐标,提取 ROI,算 BGR 均值,再用该均值填充整个 ROI。注意 ROI 必须是矩形且不越界。
-
block_size应为正整数,建议 ≥ 4,太小看不出效果,太大丢失结构 - 遍历时用
for (int i = 0; i ,不要用浮点除法四舍五入,否则块边界错位 - ROI 提取前必须检查边界:
cv::Rect roi(x, y, w, h)中w = std::min(block_size, cols - x),h = std::min(block_size, rows - y) - 均值计算用
cv::mean(roi_mat),返回cv::Scalar,注意 BGR 顺序(不是 RGB) - 填充用
roi_mat.setTo(mean_val),比循环赋值快一个数量级
cv::Mat mosaic(const cv::Mat& src, int block_size) {
cv::Mat dst = src.clone();
int rows = src.rows, cols = src.cols;
for (int y = 0; y < rows; y += block_size) {
for (int x = 0; x < cols; x += block_size) {
int w = std::min(block_size, cols - x);
int h = std::min(block_size, rows - y);
cv::Rect roi(x, y, w, h);
cv::Scalar mean = cv::mean(src(roi));
dst(roi).setTo(mean);
}
}
return dst;
}
性能关键:避免重复内存拷贝和 Mat 构造
上面函数中 src(roi) 是只读视图,开销小;但 dst(roi) 是可写视图,也无额外分配。真正拖慢速度的是频繁调用 cv::mean——它内部做通道求和与除法,对大图很敏感。
立即学习“C++免费学习笔记(深入)”;
优化方向:
- 若
block_size固定且较小(如 8 或 16),可预计算整张图的积分图(cv::integral),用 O(1) 得到任意 ROI 的像素和,再除以面积得均值 - 避免在循环内创建临时
cv::Mat对象;cv::mean返回cv::Scalar,直接传给setTo即可 - Release 模式编译 +
-O2或-O3后,上述朴素实现处理 1920×1080 图像在现代 CPU 上通常
彩色图 vs 灰度图:通道处理要一致
如果输入是灰度图(src.channels() == 1),cv::mean 返回单值 Scalar(val),setTo 会自动广播;但若强制转成三通道灰度再处理,反而增加冗余计算。最简做法是:保持原通道数,不做转换。
容易踩的坑:
- 误用
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY)再马赛克——这丢掉了原始色彩信息,且灰度化本身有加权计算,后续均值已非原始 BGR 平均 - 对 HSV 图做马赛克时,直接对 H/S/V 分量取均值会导致色相跳跃(比如红色 0° 和黄色 30° 均值是 15°,但 0° 和 180° 均值应是 90°?实际需按圆周距离处理)——除非明确需求,否则坚持用 BGR 空间
- Alpha 通道(若有)需单独处理:检测
src.depth() == CV_8UC4,然后对前 3 通道均值填充,第 4 通道另算均值或直接复制
实际应用中,块大小选择比算法本身更重要:10–20 像素适合人脸局部遮挡,50+ 像素适合快速示意性模糊;而亚像素级或非整数块尺寸,本质上已不属于传统马赛克范畴。


















