先看一张正常的图像,图像尺寸为640x558
人脸部分打赏马赛克,效果如下:
可以看出,马赛克为一个个的晶体小方格,每个小方格的各个像素颜色是一样的。
现在简单说明下,马赛克的原理。
我们列举一些数字,共5行8列,40个数字。
1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
我们不妨将这40个数字看成40个像素,马赛克就是将这些像素相邻的像素进行归一化,取这些相邻数字的平均数,然后用这个平均数替代原来的数。
比如相邻定义为2x2,即相邻的4个进行归一化,则1,2,9,10为一组;3,4,11,12为一组;17,18,25,26为一组,33,34为一组。
其中第一组1,2,9,10的和为22,取平均数5(22/4),然后将这四个数字都替换成5;经过这样的处理后,
原来的数字变成如下模式:
5 7 9 11
21 23 25 27
33 35 37 39
打马赛克就是选定区域,然后将这块区域划分若干个晶体小方格,每个晶体小方格里面的rgb三原色取平均值。
本文采取开源库cximage进行图片像素的处理。
代码不多,直接粘贴出来,其中iGridLength为晶体小方格的尺寸,iGridLength为2时,表示2x2,为4表示4x4。
注意:cximage读取jpg像素时,像素数据顺序都是从最后一行到第一行,从左到右。
// CxImageTest.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <Windows.h>
#include "cximage/ximage.h"
std::wstring string2wstring(const std::string& str)
{
std::wstring result;
//获取缓冲区大小,并申请空间,缓冲区大小按字符计算
int len = MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.size(), NULL, 0);
TCHAR* buffer = new TCHAR[len + 1];
//多字节编码转换成宽字节编码
MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.size(), buffer, len);
buffer[len] = '\0'; //添加字符串结尾
//删除缓冲区并返回值
result.append(buffer);
delete[] buffer;
return result;
}
int main()
{
CxImage image(CXIMAGE_FORMAT_JPG);
image.Load(L"E:\\learn\\c++\\cximage\\CxImageTest\\x64\\Release\\qiushuzhen_640x558.jpeg", CXIMAGE_FORMAT_JPG);
int iJpgWidth = image.GetWidth();
int iJpgHeight = image.GetHeight();
long x1 = 0;
long y1 = 0;
image.GetOffset(&x1, &y1);
const int iBeginX = 256;
const int iBeginY = 96;
const int iEndX = 424;
const int iEndY = 280;
int iRSum = 0;
int iGSum = 0;
int iBSum = 0;
int iAverageR = 0;
int iAverageG = 0;
int iAverageB = 0;
int iGridLength = 2;
for (int x = iBeginX; x < iEndX; x = x + iGridLength)
{
for (int y = iBeginY; y < iEndY; y = y + iGridLength)
{
int iPartGridXNum = 0;
int iPartGridYNum = 0;
for (int iSpanX = 0; iSpanX < iGridLength; iSpanX++)
{
if (x + iSpanX < iEndX)
{
iPartGridXNum++;
}
}
for (int iSpanY = 0; iSpanY < iGridLength; iSpanY++)
{
if (y + iSpanY < iEndY)
{
iPartGridYNum++;
}
}
iRSum = 0;
iGSum = 0;
iBSum = 0;
int iPartGridNum = iPartGridXNum * iPartGridYNum;
for (int i = x; i < x + iPartGridXNum; i++)
{
for (int j = y; j < y + iPartGridYNum; j++)
{
RGBQUAD rgb = image.GetPixelColor(i, (iJpgHeight - 1) - j, false);
iRSum += rgb.rgbRed;
iGSum += rgb.rgbGreen;
iBSum += rgb.rgbBlue;
}
}
iAverageR = iRSum / iPartGridNum;
iAverageG = iGSum / iPartGridNum;
iAverageB = iBSum / iPartGridNum;
COLORREF color = COLORREF(RGB(iAverageR, iAverageG, iAverageB));
for (int i = x; i < x + iPartGridXNum; i++)
{
for (int j = y; j < y + iPartGridYNum; j++)
{
image.SetPixelColor(i, (iJpgHeight - 1) - j, color);
}
}
}
}
std::string strSavePath = "E:\\learn\\c++\\cximage\\CxImageTest\\x64\\Release\\";
char szPicName[100] = { 0 };
sprintf(szPicName, "qiushuzhen_640x558_msk_%dx%d.jpeg", iGridLength, iGridLength);
strSavePath = strSavePath + szPicName;
std::wstring strwSavePath = string2wstring(strSavePath);
image.Save(strwSavePath.c_str(), CXIMAGE_FORMAT_JPG);
return 0;
}
本人尝试了2x2,4x4,8x8,16x16,32x32五种晶体小方格进行打马赛克,其中32x32就是本文前面部分的马赛克尺寸。
2x2的效果如下:
可以看出2x2情况下,完全看不出打了马赛克。
4x4的效果如下:
可以看出4x4已经有点马赛克效果了
8x8的效果如下:
马赛克效果很明显了
16x16的效果如下:
这时已经分辨不出是谁了。
本人就代码例子进行了百度网盘共享:
链接:https://pan.baidu.com/s/1nPO-XRcEVtD_mjuE9K2OKw
提取码:1234