C# 分析图片的主颜色

最近工作上的需要,要对40多万张照片进行主颜色的区分计算,有时候知识就是力量,若40万张图片靠人工来识别主颜色,那不知道需要多少人分辨多久才可以做好,而且还容易分辨出错,若这个事情能用程序来处理又快又好,程序足足写了1周才稳定下来,程序跑了1天就把40万张照片的主颜色全部计算出来了,技术就是力量的价值观又一次得到了验证。

   朋友多好办事,其中得到一个好朋友的帮助,改进了颜色区分判断的函数,也对工作起了很大作用。朋友多好办事、平时真需要多与各种朋友往来。

   分析图片的主颜色区间过程中遇到的主要难题:

 1: 网上类似的C#的参考代码比较少。
 2: 我们本身不是研究图像处理技术的。
 3: 需要把40万张图片进行处理。
 4: 网络服务器不稳定时,也需要能稳定运行。
 5: 颜色计算的程序效率要高,不能占用过多服务器资源,需要运行稳定。

计算好的图片的分布情况,还比较均匀。

测试程序的效果如下:

参考代码如下:

复制代码
//-----------------------------------------------------------------
// All Rights Reserved , Copyright (C) 2012 , Hairihan TECH, Ltd. 
//-----------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Configuration;
using System.Threading;

using DotNet.Utilities;

namespace PrimaryColors
{
    /// <summary>
    /// FormPrimaryColors 主颜色计算
    /// 
    /// 修改纪录
    ///        2013.01.22 版本:1.0 吉日嘎拉  创建。  
    ///        
    /// 版本:1.0
    /// <author>
    ///        <name>吉日嘎拉</name>
    ///        <date>2013.01.22</date>
    /// </author> 
    /// </summary>
    public partial class FormPrimary : Form
    {
        public FormPrimary()
        {
            InitializeComponent();
        }

        private void GetColor()
        {
            btnColor.BackColor = Color.FromArgb(this.tbR.Value, this.tbG.Value, this.tbB.Value);
            this.btnColor.Text = this.tbR.Value.ToString() + ":" + this.tbG.Value.ToString() + ":" + this.tbB.Value.ToString();
            // this.Text = btnColor.BackColor.GetHue().ToString("f0") + "-" + btnColor.BackColor.GetSaturation().ToString("f2") + "-" + btnColor.BackColor.GetBrightness().ToString("f2");
        }

        private void tb_Scroll(object sender, EventArgs e)
        {
            GetColor();
        }

        // 取设备场景,返回设备场景句柄
        [DllImport("user32.dll")]
        private static extern IntPtr GetDC(IntPtr hdc);

        // 取指定点颜色
        [DllImport("gdi32.dll")]
        private static extern int GetPixel(IntPtr hdc, Point point);

        private void picImage_Click(object sender, EventArgs e)
        {
            //取置顶点坐标 
            Point point = new Point(MousePosition.X, MousePosition.Y);
            //取到设备场景(0就是全屏的设备场景) 
            IntPtr hdc = GetDC(new IntPtr(0));

            int c = GetPixel(hdc, point);//取指定点颜色 
            int r = (c & 0xFF);//转换R 
            int g = (c & 0xFF00) / 256;//转换G 
            int b = (c & 0xFF0000) / 65536;//转换B

            this.tbR.Value = r;
            this.tbG.Value = g;
            this.tbB.Value = b;
            GetColor();
        }

        private void FormPrimaryColors_DragOver(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Move;
            }
        }

        private void FormPrimaryColors_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                string[] folder = (string[])e.Data.GetData(DataFormats.FileDrop);
                for (int i = 0; i <= folder.Length - 1; i++)
                {
                    if (System.IO.File.Exists(folder[i]))
                    {
                        SetTargetImage(folder[i]);
                    }
                }
            }
        }

        private void Analyse(bool fromFile = true)
        {
            int boundaryValue = 15;
            if (!string.IsNullOrEmpty(this.txtBoundaryValue.Text))
            {
                boundaryValue = int.Parse(this.txtBoundaryValue.Text);
            }

            // 获取颜色表
            Dictionary<Color, int> colors = new Dictionary<Color, int>();
            // 初始化计数器
            for (int i = 0; i < flpColors2.Controls.Count; i++)
            {
                if (!colors.ContainsKey(flpColors2.Controls[i].BackColor))
                {
                    colors.Add(flpColors2.Controls[i].BackColor, 0);
                }
            }

            // 进行颜色区间计算
            int width = 0;
            if (!string.IsNullOrEmpty(this.txtWidth.Text))
            {
                width = int.Parse(this.txtWidth.Text);
            }

            string character = string.Empty;
            Double binary = 0;
            // "红色","橙色","黄色","绿色","青色","蓝色","紫色","粉红色","白色","灰色","黑色","棕色"
            // 进行图像分析
            if (fromFile)
            {
                colors = PrimaryColors.AnalyseFromFile(this.txtFile.Text, width, colors, boundaryValue, out character, out binary);
            }
            else
            {
                colors = PrimaryColors.AnalyseFromUrl(this.txtFile.Text, width, colors, boundaryValue, out character, out binary);
            }

            this.txtCharacter.Text = character;
            this.txtBinary.Text = binary.ToString();

            // 显示计数器
            for (int i = 0; i < flpColors2.Controls.Count; i++)
            {
                // 初始化
                flpColors2.Controls[i].Text = colors[flpColors2.Controls[i].BackColor].ToString();
            }
        }

        /// <summary>
        /// 把目标颜色归类为主颜色
        /// </summary>
        /// <param name="color">当前颜色</param>
        /// <param name="boundaryValue">边界值</param>
        private void ConvertIntoPrimaryColor(Color color, int tolerance)
        {
            Color nearest_color = Color.Empty;
            // 算法一
            for (int i = 0; i < flpColors1.Controls.Count; i++)
            {
                Color o = flpColors1.Controls[i].BackColor;

                // compute the Euclidean distance between the two colors
                // note, that the alpha-component is not used in this example
                double dbl_test_red = Math.Pow(Convert.ToDouble(((Color)o).R) - color.R, 2.0);
                double dbl_test_green = Math.Pow(Convert.ToDouble(((Color)o).G) - color.G, 2.0);
                double dbl_test_blue = Math.Pow(Convert.ToDouble(((Color)o).B) - color.B, 2.0);

                double temp = Math.Sqrt(dbl_test_blue + dbl_test_green + dbl_test_red);
                // explore the result and store the nearest color
                if (temp < tolerance)
                {
                    flpColors1.Controls[i].Tag = (int)flpColors1.Controls[i].Tag + 1;
                    break;
                }
            }

            //  算法二
            Color backColor;
            for (int i = 0; i < flpColors1.Controls.Count; i++)
            {
                backColor = flpColors1.Controls[i].BackColor;
                if ((color.R + tolerance > backColor.R && color.R - tolerance < backColor.R)
                    && (color.G + tolerance > backColor.G && color.G - tolerance < backColor.G)
                    && (color.B + tolerance > backColor.B && color.B - tolerance < backColor.B))
                {
                    flpColors1.Controls[i].Tag = (int)flpColors1.Controls[i].Tag + 1;
                    break;
                }
            }
        }

        private void btnAnalyse_Click(object sender, EventArgs e)
        {
            Analyse(true);
        }

        private void btnColor_Click(object sender, EventArgs e)
        {
            int width = 0;
            if (!string.IsNullOrEmpty(this.txtWidth.Text))
            {
                width = int.Parse(this.txtWidth.Text);
            }
            this.picTarget.Image = PrimaryColors.GetThumbnailImageFromUrl(this.txtFile.Text, width);

            Analyse(false);
        }

        private void SetTargetImage(string fileName)
        {
            txtFile.Text = fileName;
            int width = int.Parse(this.txtWidth.Text);
            this.picImage.BackgroundImage = Image.FromFile(fileName);
            picTarget.Image = PrimaryColors.GetThumbnailImageFromFile(fileName, width);
        }

        private void btnSelect_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "jpg files (*.jpg)|*.jpg|All files (*.*)|*.*";
            openFileDialog.FilterIndex = 1;
            openFileDialog.RestoreDirectory = true;
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                SetTargetImage(openFileDialog.FileName);
            }
        }

        private void btnConverter_Click(object sender, EventArgs e)
        {
            this.txtBinary.Text = PrimaryColors.CharacterToBinary(this.txtCharacter.Text).ToString();
        }

        private void btnBinaryToCharacter_Click(object sender, EventArgs e)
        {
            this.txtCharacter.Text = Convert.ToString(int.Parse(this.txtBinary.Text), 2);
            this.txtCharacter.Text = this.txtCharacter.Text.PadLeft(12, '0');
        }

        private int DbAnalyse()
        {
            int returnValue = 0;
            int width = 0;
            if (!string.IsNullOrEmpty(this.txtWidth.Text))
            {
                width = int.Parse(this.txtWidth.Text);
            }
            int boundaryValue = 15;
            if (!string.IsNullOrEmpty(this.txtBoundaryValue.Text))
            {
                boundaryValue = int.Parse(this.txtBoundaryValue.Text);
            }
            // 获取颜色表
            Dictionary<Color, int> colors = new Dictionary<Color, int>();
            // 初始化计数器
            for (int i = 0; i < flpColors2.Controls.Count; i++)
            {
                if (!colors.ContainsKey(flpColors2.Controls[i].BackColor))
                {
                    colors.Add(flpColors2.Controls[i].BackColor, 0);
                }
            }

            string imgUrl = string.Empty;
            string character = string.Empty;
            Double binary = 0;
            string id = string.Empty;
            
            // 计算过一遍的不要再重复计算
            // SELECT COUNT(1) FROM HuiTu_Pic_20130123.dbo.ah_pic_pass
            // int maxId = 0;
            
            string commandText = string.Empty;

            // commandText = "SELECT MAX(pic_id) FROM ah_pic_pass WHERE pic_Colors IS NOT NULL OR pic_Colors != 0";
            // Object maxObject = DbHelper.ExecuteScalar(commandText);
            // if (maxObject != null && maxObject != DBNull.Value)
            // {
            //    maxId = int.Parse(maxObject.ToString());
            // }

            commandText = "SELECT TOP 1000 id, imgUrl FROM pic WHERE Colors IS NOT NULL ORDER BY NEWID()";
            DataTable dt = DbHelper.Fill(commandText);
            returnValue = dt.Rows.Count;
            int y = 0;
            foreach (DataRow dr in dt.Rows)
            {
                y++;
                id = dr["id"].ToString();
                imgUrl = dr["imgUrl"].ToString();
                // this.txtFile.Text = imgUrl;
                PrimaryColors.AnalyseFromUrl(imgUrl, width, colors, boundaryValue, out character, out binary);
                if (binary > 0)
                {
                    commandText = "UPDATE pic SET Colors = " + binary + " WHERE id = " + id;
                    DbHelper.ExecuteNonQuery(commandText);
                    this.Text = y.ToString() + "/" + dt.Rows.Count.ToString() + ":" + id;
                }
            }
            return returnValue;
        }

        private void btnDbAnalyse_Click(object sender, EventArgs e)
        {
            System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;
            this.btnDbAnalyse.Enabled = false;
            // 数据库连接串
            DbHelper.DbConnection = ConfigurationManager.AppSettings["BusinessDbConnection"];
            this.bgwColors.RunWorkerAsync();
            // Thread thread = new Thread(new ThreadStart(DbAnalyse));
            // thread.Start();
        }

        private void FormPrimary_Load(object sender, EventArgs e)
        {
            this.txtWidth.Text = ConfigurationManager.AppSettings["Width"];
            this.txtBoundaryValue.Text = ConfigurationManager.AppSettings["BoundaryValue"];
        }

        private void btn_Click(object sender, EventArgs e)
        {
            this.txtFile.Text = string.Format("{0},{1},{2}", ((Button)sender).BackColor.R, ((Button)sender).BackColor.G, ((Button)sender).BackColor.B);
        }

        private void bgwColors_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            int returnValue = 0;
            while (!this.IsDisposed)
            {
                try
                {
                    returnValue = DbAnalyse();
                }
                catch (Exception ex)
                {
                    // 在本地记录异常
                    FileUtil.WriteException(ex);
                }
                if (returnValue == 0)
                {
                    Thread.Sleep(1000 * 100);
                }
            }
        }

        // 01:[ok] 图片上的颜色点选功能的优化。
        // 02:[ok] 变量名控件名优化。
        // 03:[ok] 图片可以支持拖拽。
        // 04:[ok] 图片压缩参数可以设置。
        // 05:[ok] 图片可以直接通过路径读取。
        // 06:[ok] 可以读取 web.config 里的参数。
        // 07:[ok] 代码加上注释。
        // 08:[ok] 3个主颜色能计算出来。
        // 09:[ok] 3个主颜色可以按2进制保存。
        // 10:[ok] 图像的缩放的实现。
        // 11:[ok] 可以按dll方式调用。
        // 12:[ok] 从网址计算图片的主颜色。
        // 13:[ok] 可以保存数据库。
        // 14:[ok] 相关的查询页面可以进行颜色区分,这个目录都能进行主颜色计算。
        // 15:[ok] 20000张图片的识别测试,看看识别效率如何。
    }
}
复制代码
复制代码
//-----------------------------------------------------------------
// All Rights Reserved , Copyright (C) 2012 , Hairihan TECH, Ltd. 
//-----------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Configuration;
using System.Linq;

namespace PrimaryColors
{
    /// <summary>
    /// FormPrimaryColors 主颜色计算
    /// 
    /// 修改纪录
    ///        2013.01.22 版本:1.0 吉日嘎拉  创建。  
    ///        
    /// 版本:1.0
    /// <author>
    ///        <name>吉日嘎拉</name>
    ///        <date>2013.01.22</date>
    /// </author> 
    /// </summary>
    public partial class PrimaryColors
    {
        public PrimaryColors()
        {
        }

        /// <summary>
        /// 将类似2进制字符串 10010110111 转换为 数值型 
        /// </summary>
        /// <param name="character">字符串</param>
        /// <returns>返回数值类型</returns>
        public static double CharacterToBinary(string character)
        {
            double returnValue = 0;
            int j = 0;
            // 截取字符串
            for (int i = 0; i < character.Length; i++)
            {
                if (character[i].Equals('1'))
                {
                    // 2的几次幂
                    j = character.Length - i - 1;
                    returnValue = returnValue + System.Math.Pow(2, j);
                }
            }
            return returnValue;
        }

        /// <summary>
        /// 获取主颜色字符串编码例如 01011010
        /// </summary>
        /// <param name="colors">颜色字典</param>
        /// <returns>主颜色表</returns>
        private static string GetPrimaryColors(Dictionary<Color, int> colors)
        {
            string returnValue = string.Empty;
            // 这里计算已经获得了接近的颜色总数
            Double pixelCount = 0;
            foreach (KeyValuePair<Color, int> pair in colors)
            {
                pixelCount += pair.Value;
            }
            // 进行转换
            List<KeyValuePair<Color, int>> keyValueList = new List<KeyValuePair<Color, int>>(colors);
            // 排序
            keyValueList.Sort(
                delegate(KeyValuePair<Color, int> firstPair,
                KeyValuePair<Color, int> nextPair)
                {
                    return nextPair.Value.CompareTo(firstPair.Value);
                });

            // 主颜色计算
            bool isPrimaryColor = false;
            foreach (KeyValuePair<Color, int> pair in colors)
            {
                isPrimaryColor = false;
                if (pair.Key == keyValueList[0].Key
                    || pair.Key == keyValueList[1].Key
                    || pair.Key == keyValueList[2].Key)
                    // 要求颜色能达到一定的比例才计算在主颜色里
                    if (pair.Value > pixelCount / (colors.Count / 2))
                    {
                        isPrimaryColor = true;
                    }
                // 字符串颜色表
                returnValue += isPrimaryColor ? "1" : "0";
            }
            return returnValue;
        }

        public static bool ThumbnailCallback() { return false; }

        private static System.Net.WebClient webClient = null;

        private static System.Net.WebClient CurrentWebClient
        {
            get
            {
                if (webClient == null)
                {
                    webClient = new System.Net.WebClient();
                }
                return webClient;
            }
        }

        /// <summary>
        /// 获取压缩的图片
        /// </summary>
        /// <param name="imgUrl">网址路径</param>
        /// <param name="width">宽度</param>
        /// <returns>自动压缩后的图片</returns>
        public static Bitmap GetThumbnailImageFromUrl(string imgUrl, int width)
        {
            // 远程图片路径 
            // string imgUrl = http://www.海日涵.com/img/baidu_sylogo1.gif;
            // 读取远程图片数据 
            byte[] bytes = CurrentWebClient.DownloadData(imgUrl);
            // 将二进制转换为图片对象  
            System.Drawing.Image image = System.Drawing.Image.FromStream(new System.IO.MemoryStream(bytes));
            int height = image.Height;
            if (width == 0)
            {
                width = image.Width;
            }
            else
            {
                height = (width * image.Height) / image.Width;
            }
            return new Bitmap(image.GetThumbnailImage(width, height, ThumbnailCallback, IntPtr.Zero));
        }

        /// <summary>
        /// 获取压缩的图片
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <param name="width">宽度</param>
        /// <returns>自动压缩后的图片</returns>
        public static Bitmap GetThumbnailImageFromFile(string fileName, int width)
        {
            Image image = Image.FromFile(fileName);
            int height = image.Height;
            if (width == 0)
            {
                width = image.Width;
            }
            else
            {
                height = (width * image.Height) / image.Width;
            }
            return new Bitmap(image.GetThumbnailImage(width, height, ThumbnailCallback, IntPtr.Zero));
        }

        /// <summary>
        /// 分析图片颜色
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <param name="width">压缩宽度</param>
        /// <param name="colors">需要匹配的颜色列表</param>
        /// <param name="boundaryValue">色差系数</param>
        /// <param name="binary">转换为对应的数值</param>
        /// <returns>分析结果列表</returns>
        public static Dictionary<Color, int> AnalyseFromFile(string fileName, int width, Dictionary<Color, int> colors, int boundaryValue, out string character, out Double binary)
        {   
            Bitmap bitmap = GetThumbnailImageFromFile(fileName, width);
            return Analyse(bitmap, width, colors, boundaryValue, out character, out binary);
        }

        public static Dictionary<Color, int> AnalyseFromUrl(string imgUrl, int width, Dictionary<Color, int> colors, int boundaryValue, out string character, out Double binary)
        {
            Bitmap bitmap = GetThumbnailImageFromUrl(imgUrl, width);
            return Analyse(bitmap, width, colors, boundaryValue, out character, out binary);
        }

        public static Dictionary<Color, int> Analyse(Bitmap bitmap, int width, Dictionary<Color, int> colors, int boundaryValue, out string character, out Double binary)
        {
            colors = ConvertIntoPrimaryColor(bitmap, colors, boundaryValue);
            character = GetPrimaryColors(colors);
            binary = CharacterToBinary(character);
            return colors;
        }
    }
}
复制代码


欢迎高手贴出个更加强大的图片主颜色分析算法。

同时运行10来次程序,并行计算图片的主颜色区间,这样效率提高了很多

将权限管理、工作流管理做到我能力的极致,一个人只能做好那么很少的几件事情。
原文地址:https://www.cnblogs.com/Leo_wl/p/2876993.html