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来次程序,并行计算图片的主颜色区间,这样效率提高了很多
将权限管理、工作流管理做到我能力的极致,一个人只能做好那么很少的几件事情。
作者: Leo_wl
出处: http://www.cnblogs.com/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息声明:本文来自网络,不代表【好得很程序员自学网】立场,转载请注明出处:http://haodehen.cn/did47058