“万恶”的马赛克是怎么实现的呢?

最近参加[声网的RTC开发者大赛](https://segmentfault.com/page/rtc-
hackathon-2020)开发了一个陌生人[视频聊天应用](https://github.com/Luomingbear/RTC-
Hackathon/tree/master/SDKChallengeProject/likemosaic),为了降低聊天时的紧张感,对视频画面进行马赛克处理。此文便是对马赛克算法实现的总结。

原理

图片是由一个个像素点组成的,由于间距很小,便形成了图像的效果。将图片变成马赛克样式的本质就是修改像素点,使得一定范围内的像素用相同的颜色值表示。这里的“一定范围”我们暂且称之为马赛克块,马赛克块的大小也就决定了图片马赛克程度的不同。以3x3的像素点大小的马赛克块为例,原始像素点的值为1...9,修改之后全部为n,如下图所示。

根据这个原理,我们只需要将图片分为一个一个的马赛克块,然后遍历它们,使每一个马赛克块用相同的颜色值表示即可。

确定马赛克块坐标

将一个马赛克块看作一个整体,图片就被分成了m_width * m_height个马赛克块,那么对于横坐标x,纵坐标y的马赛克块的下标可以通过如下的公式获得。

index = (y - 1) * m_width + x

确定颜色值

得到马赛克块之后,就可以把这个区域的像素点修改为同一个值了,这里有几个思路,分别是

  1. 使用左上角的值直接替换其他点的值;
  2. 计算区域像素点的平均值然后替换每一个点;
  3. 使用区域内像素点的中位数值作为其他点的值。

三种方案在显示效果和计算速度上也有差异,方案1计算快,但是显示会不够平滑。方案2显示最为平滑,计算量比方案1多,但是比方案3更少。方案3因为需要求出中位数,会增加很多比较操作,所以计算量最多,显示效果上也没有平均值的方案平滑,但是轮廓感更强。

颜色空间

上面仅仅是在理论层面介绍了如何实现马赛克效果,但是在实际的编码中,我们还需要了解图形在数据里面是怎么表示的。这就涉及到 颜色空间
的概念了,常见的颜色空间有RGB、YUV和CMYK(主要应用于打印场景,本文不讲解)等。

RGB

RGB颜色空间是根据颜色在自然界的产生原理来设计的,把颜色分为R:红色、G:绿色和B:蓝色,也就是三原色。一般使用RGB24(也称为RGB888)的颜色空间,即RGB三个分量各使用8bit表示,所以一张图片占用的内存大小就是width _height_ 3字节。RGB颜色空间在计算机里面主要用于图形的采集、显示等领域,例如我们的显示屏显示画面时会使用RGB颜色空间对每一个像素点进行赋值显示。
RGB24的数据排列方式为按照像素点将数据分组,每一组有RGB三个分量,对于每一组数据,按照B->G->R的顺序排列,如下图所示。

YUV

YUV颜色空间将颜色分为了亮度分量Y和色度分量U和V,因为YUV将亮度分量独立了出来,所以可以兼容老的黑白电视机。常见的YUV格式有YUV444、YUV422和YUV420。对于YUV444、YUV422和YUV420格式的区别,主要是UV分量的分布,如图,空心圆表示UV分量,实心圆表示Y分量。

使用YUV420格式相比RGB可以减少一半的带宽,YUV420格式占用的内存大小为width _height + width_ height /4 + width * height /4字节。

由于YUV420可以节省大量带宽,而人眼对色度信息没有亮度敏感,所以观感不会有明显区别,日常应用最为广泛。根据UV分量在数据中的排布方式,YUV420又分为I420、NV12和YV12等格式。

I420 I420格式的数据排布如下图所示,先将Y分量全部排进去,然后排U分量最后是V分量。

YV12 YV12格式的数据排布如下图所示,先将Y分量全部排进去,然后排V分量最后是U分量。

NV12 NV12格式的数据排布如下图所示,先将Y分量全部排进去,然后U和V分量交替排列。

C语言实现

下面给出I420格式及RGB24格式图像进行马赛克处理的C语言实现,其中马赛克块的颜色值使用平均值法获得,当然此算法还有很多可以改进的地方,例如可考虑加入多线程并发处理提高算法效率。

  • 输入RGB格式数据

    /**

    • 对像素数据进行马赛克处理

    • input_data : 输入的原始像素数据

    • width : 图片的宽度

    • height: 图片的高度

    • channel : 图像通道数,例如RGB24则有3个通道

    • scale : 马赛克块的大小

    • return : 处理之后的像素数据

    • /
      void mosaic(unsigned char *input_data, unsigned char *out_data,

            int width, int height, int channel, int scale)
      

      {
      int index, tindex;
      int pix[channel];
      for (int i = 0; i < height; i += scale)
      {

        for (int j = 0; j < width; j += scale)
        {
            index = (width * i + j) * channel;
            for (int d = 0; d < channel; d++)
            {
                pix[d] = 0;
            }
      
            //平均值
            for (int k = 0; k < scale; k++)
            {
                for (int p = 0; p < scale; p++)
                {
                    tindex = index + (k * width + p) * channel;
                    if (tindex < width * height * channel - channel)
                    {
                        for (int d = 0; d < channel; d++)
                        {
                            pix[d] += input_data[tindex + d];
                        }
                    }
                }
            }
            for (int d = 0; d < channel; d++)
            {
                pix[d] = pix[d] / scale / scale;
            }
      
            for (int k = 0; k < scale; k++)
            {
                for (int p = 0; p < scale; p++)
                {
                    tindex = index + (k * width + p) * channel;
                    if (tindex < width * height * channel - channel)
                    {
                        for (int d = 0; d < channel; d++)
                        {
                            out_data[tindex + d] = pix[d];
                        }
                    }
                }
            }
        }
      

      }
      }

  • 输入I420格式数据

    /**

    • 对I420格式图像进行马赛克处理

    • input_y : 输入数据的y分量

    • input_u : 输入数据的u分量

    • input_v : 输入数据的v分量

    • out_y : 输出数据的y分量

    • out_u : 输出数据的u分量

    • out_v : 输出数据的v分量

    • width : 图片的宽度

    • height: 图片的高度

    • scale : 马赛克块的大小

    • /
      void mosaicyuv(unsigned char *input_y, unsigned char *input_u, unsigned char *input_v,

               unsigned char *out_y, unsigned char *out_u, unsigned char *out_v,
               int width, int height, int scale)
      

      {
      int len = width * height;
      memcpy(out_y, input_y, len);
      memcpy(out_u, input_u, len / 4);
      memcpy(out_v, input_v, len / 4);
      int index, tindex, y;
      for (int i = 0; i < height; i += scale)
      {

        for (int j = 0; j < width; j += scale)
        {
            index = width * i + j;
            y = out_y[index];
      
            for (int k = 0; k < scale; k++)
            {
                for (int p = 0; p < scale; p++)
                {
                    tindex = index + (k * width + p);
                    if (tindex < len)
                    {
                        y += out_y[tindex];
                    }
                }
            }
            y = y / scale / scale;
            for (int k = 0; k < scale; k++)
            {
                for (int p = 0; p < scale; p++)
                {
                    tindex = index + (k * width + p);
                    if (tindex < len)
                    {
                        out_y[tindex] = y;
                    }
                }
            }
        }
      

      }

      //处理UV分量
      int u, v;
      index = tindex = u = v = 0;
      scale = scale / 2;
      for (int i = 0; i < height / 2; i += scale)
      {

        for (int j = 0; j < width / 2; j += scale)
        {
            index = width / 2 * i + j;
            u = v = 0;
            for (int k = 0; k < scale; k++)
            {
                for (int p = 0; p < scale; p++)
                {
                    tindex = index + (k * width / 2 + p);
                    if (tindex < len / 4)
                    {
                        u = u + out_u[tindex];
                        v = v + out_v[tindex];
                    }
                }
            }
            u = u / scale / scale;
            v = v / scale / scale;
      
            for (int k = 0; k < scale; k++)
            {
                for (int p = 0; p < scale; p++)
                {
                    tindex = index + (k * width / 2 + p);
                    if (tindex < len / 4)
                    {
                        out_u[tindex] = u;
                        out_v[tindex] = v;
                    }
                }
            }
        }
      

      }
      }

Search by:GoogleBingBaidu