基于c#的海量图片查重去重

        本文主要讲解了我们针对百万级的图片素材如何进行查重、去重。

        朋友在运营中的一个网站,91素材 https://www.91sc.com/,这个网站主要是提供PPT模板、Word模板、图片素材类的下载。

        随着运营年限的不断增加,网站的存储容量突飞猛进,从而也带来的以下的问题:

  1. 1)用户上传的图片素材不停的增长,数据量已经达到百万级,以后这个量还会不断累加。

  2. 2)由于是自主上传,导致网站充斥着大量的重复素材,给审核工作带来挑战,同时也极大的浪费了网站的空间存储。

        如何对现有百万级的图片素材、以及用户实时上传的素材进行快速有效的甄别,研发项目组进行了研究与探讨,在云识别与自主识别两种方案的基础上,最终选择了第二种方案。

        方案一:利用百度云、某云的图像搜索,两家都差不多,首先是将图片存到云端,数据量有限制,一般是10~20万(这个目前满足不了我们的百万级图片量),然后是上传某张图片与云端已已有的图片数据进行比较,收费一般在1500元/10W次接口调用,有有效期限制,貌似百度云还分入库、检查等的接口调用。这种方案就导致费用高、不能自主扩展、实时调用有延迟会影响用户体验。所以暂时不考虑这种方案,暂时放弃!

        方案二:自主研发识别算法

        基于c#的图片识别功能实现,能够满足我们当前的需要,可供大家借鉴,不一定适合所有遇到海量图片查重去重的朋友们,勿喷,~_~。

        实现的原理如下:

        现有网站的素材资源,均是以图片形式存储,PPT,Word之类的也是逐页转图片,我们的思路是,如素材有多张图片,我们只针对第一张图片进行建模录对应的图片指纹,然后按照一定的算法去与图片指纹库进行一一比较,得出比较的2张图片之间的相似度并记录,然后在审核时,列出超过一定相似度的图片,例如80%,人工观察,确定通过还是删除。

        这种方案优点在于,不花钱、任意开发并与系统结合、随时随地用;缺点在于虽然用了多线程,但效率也不算高,运行的机器性能还可以,轮循比较100W左右的图片在5小时左右。

        下面主要讲解一下如何,利用c#技术,多线程、建图片指纹库、比较等步骤的讲解,文后附上相关代码的下载地址。

        最终的审核效果图如下图,将相似度超过95%的图片,列出来,供维护人员进行处理。

 第一阶段:多线程提取 图片指纹

重点:指纹的原理,其实就是将图片缩放成类似于(256, 256)(128, 128)的等比例位图,每个像素点超过平均值为1,否则为0,指纹是由1和0组成的字符串,像素高则对比度高但性能慢,反之。

 第二阶段:多线程提取 图片查重

原理,就是均对图片指纹数据源进行一对一的比较,根据一定的算法,得出两者之间的相似度。例如最终得出图片A的相似日志{图片B:85,......图片H:99} ,自己可以增加过滤条件,例如相似度低于70%的,就不记录。

我们一次性相指纹数据加载,有点耗内存,将加载的数据分段多线程处理,以提升效率。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //下载供比较的图片
            ImgSave("1.png", "https://www.91sc.com/UploadFile/img/pic11661178.png");
            ImgSave("2.png", "https://www.91sc.com/UploadFile/img/pic11665223.png");
            string path = System.Environment.CurrentDirectory + "\\";

            //以下生成两张图片的指纹

            string fileName = path + "1.png";
            Bitmap baseBM = new Bitmap(FileToBitmap(fileName), 256, 256);
            int[] baseBit = GetHisogram(baseBM);
            string[] strs = Array.ConvertAll<int, string>(baseBit, delegate (int input) { return input.ToString(); });
            string fingerPrint1 = string.Join(",", strs);
            baseBM.Dispose();
            File.Delete(fileName);

            fileName = path + "2.png";
            baseBM = new Bitmap(FileToBitmap(fileName), 256, 256);
            baseBit = GetHisogram(baseBM);
            strs = Array.ConvertAll<int, string>(baseBit, delegate (int input) { return input.ToString(); });
            string fingerPrint2 = string.Join(",", strs);
            baseBM.Dispose();
            File.Delete(fileName);

            //开始比较两张图片的指纹
            string[] baseArr1 = fingerPrint1.Split(',');
            int[] baseBit1 = Array.ConvertAll(baseArr1, int.Parse);
            string[] baseArr2 = fingerPrint2.Split(',');
            int[] baseBit2 = Array.ConvertAll(baseArr2, int.Parse);

            int result = Convert.ToInt32(SimilarImage.GetResult(baseBit1, baseBit2) * 100);

            MessageBox.Show("两张图片,相似度为 " + result + " %。");

            //说明:超过一定的相似度值,说明两者相似

        }


        /// <summary>
        /// 下载网络图片
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="url"></param>
        public void ImgSave(string fileName, string url)
        {
            //url = "https://m.91sc.com/UploadFile/img/pic11661178.png";
            WebRequest imgRequest = WebRequest.Create(url);

            HttpWebResponse res;
            try
            {
                res = (HttpWebResponse)imgRequest.GetResponse();
            }
            catch (WebException ex)
            {
                res = (HttpWebResponse)ex.Response;
            }

            if (res.StatusCode.ToString() == "OK")
            {
                System.Drawing.Image downImage = System.Drawing.Image.FromStream(imgRequest.GetResponse().GetResponseStream());

                string path = System.Environment.CurrentDirectory + "\\";
                downImage.Save(path + fileName);
                downImage.Dispose();
            }
        }

        /// <summary>
        /// 图片转Bitmap
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static Bitmap FileToBitmap(string fileName)
        {
            // 打开文件  
            FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
            // 读取文件的 byte[]  
            byte[] bytes = new byte[fileStream.Length];
            fileStream.Read(bytes, 0, bytes.Length);
            fileStream.Close();
            // 把 byte[] 转换成 Stream  
            Stream stream = new MemoryStream(bytes);

            stream.Read(bytes, 0, bytes.Length);
            // 设置当前流的位置为流的开始  
            stream.Seek(0, SeekOrigin.Begin);

            MemoryStream mstream = null;
            try
            {
                mstream = new MemoryStream(bytes);
                return new Bitmap((Image)new Bitmap(stream));
            }
            catch (ArgumentNullException ex)
            {
                return null;
            }
            catch (ArgumentException ex)
            {
                return null;
            }
            finally
            {
                stream.Close();
            }
        }


        public int[] GetHisogram(Bitmap img)
        {
            BitmapData data = img.LockBits(new System.Drawing.Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            int[] histogram = new int[256];
            unsafe
            {
                byte* ptr = (byte*)data.Scan0;
                int remain = data.Stride - data.Width * 3;
                for (int i = 0; i < histogram.Length; i++)
                    histogram[i] = 0;
                for (int i = 0; i < data.Height; i++)
                {
                    for (int j = 0; j < data.Width; j++)
                    {
                        int mean = ptr[0] + ptr[1] + ptr[2];
                        mean /= 3;
                        histogram[mean]++;
                        ptr += 3;
                    }
                    ptr += remain;
                }
            }
            img.UnlockBits(data);
            return histogram;
        }
    }


    public class SimilarImage
    {
        public SimilarImage()
        {

        }

        public static float GetResult(int[] firstNum, int[] scondNum)
        {

            if (firstNum.Length != scondNum.Length)
            {
                return 0;
            }
            else
            {
                float result = 0;
                int j = firstNum.Length;
                for (int i = 0; i < j; i++)
                {
                    result += 1 - GetAbs(firstNum[i], scondNum[i]);
                    //Console.WriteLine(i + "----" + result);
                }
                return result / j;
            }
        }

        private static float GetAbs(int firstNum, int secondNum)
        {
            float abs = Math.Abs((float)firstNum - (float)secondNum);
            float result = Math.Max(firstNum, secondNum);
            if (result == 0)
                result = 1;
            return abs / result;

        }
    }

说明:本文为了方便 ,特地将核心代码进行了提取与改写,脱离了项目环境,方便网友使用。

演示代码下载 https://www.91sc.com/code/code-12983967.html

如有问题或交流请留言。


版权声明

此作品来源于第三方分享者设计上传,其版权归原创作者拥有。若作为商业用途,请获取模板原作者授权或替换相应素材,相关字体以及人物肖像需版权方额外授权,请谨慎使用,91素材不承担由此引发的一切版权纠纷。

91素材尊重知识产权,如知识产权权利人认为平台内容涉嫌侵权,可联系我们,我们将及时处理。

网站提供的党政主题相关内容(国旗、国徽、党徽...),目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

  • 编号:12983967 收藏 举报
  • 大小:854 K
  • 版权: 共享源码
  • 上传时间:2021-11-26

您可能还喜欢