最近工作上的需要,要对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来次程序,并行计算图片的主颜色区间,这样效率提高了很多
将权限管理、工作流管理做到我能力的极致,一个人只能做好那么很少的几件事情。