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://HdhCmsTest 海日涵 测试数据/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://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息声明:本文来自网络,不代表【好得很程序员自学网】立场,转载请注明出处:http://www.haodehen.cn/did47058