/// <summary>
/// 返回一个16位hash码(先将图片转成灰度, 再分块得到每个分块的灰度值(0-255), 再开方, 得0-15值. 正好用16进制数表示
/// </summary>
/// <param name="strPicPath">图片路径</param>
/// <returns>16位hash码</returns>
public string GetPictureHashCode(string strPicPath)
{
try
{
//如果传的是字节数组可以用MemoryStream来读取
int iHBlockNum = 4; //图片水平切块数
int iVBlockNum = 4; //图片竖直切块数
long[] arrayBright = new long[iHBlockNum * iVBlockNum]; //用来存放每个块里面所有灰度图像亮度值之和
int[] arrayPixelNumber = new int[iHBlockNum * iVBlockNum]; //用来存放每个块的像素个数
// Create a new bitmap.
Bitmap bitmap = new Bitmap(strPicPath);
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
// Lock the bitmap's bits.
//转成24rgb颜色 24色就是由r g b, 三个颜色, 每个颜色各用一字节(8位)表示亮度
BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
//图片一行象素所占用的字节 本应是3*bitmap.Width的, 但有内存补齐, 一般是4的位数, 实际是大于3*bitmap.Width
int iStride = bmpData.Stride;
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int iBytes = iStride * bitmap.Height;
byte[] rgbValues = new byte[iBytes];
// Copy RGB values into the Array
Marshal.Copy(ptr, rgbValues, 0, iBytes);
// Unlock the bits.
bitmap.UnlockBits(bmpData);
//水平方向每个块的长度 (这里全用整数来除, 得到的值也是整数, bmpData.Width / iOffsetX 的值不会超过 iHBlockNum)
int iOffsetX = bmpData.Width / iHBlockNum;
//竖直方向每个块的长度
int iOffsetY = bmpData.Height / iVBlockNum;
for (int y = 0; y < bmpData.Height; ++y)
{
for (int x = 0; x < bmpData.Width; ++x)
{
//图像(x, y)坐标坐标中第1个像素中rgbValues中的索引位置(这儿x,y是从0开始记的)
//rgbValues每行是扫描宽个字节, 不是bitmap.Width * 3
int iThird = iStride * y + 3 * x;
//计算灰度值
byte avg = (byte)((rgbValues[iThird] + rgbValues[iThird + 1] + rgbValues[iThird + 2]) / 3);
//灰度图
//rgbValues[iThird] = avg;
//rgbValues[iThird + 1] = avg;
//rgbValues[iThird + 2] = avg;
//计算点在哪个区里面 y / iOffsetY 是取除后的整数值0.9算0
//水平最后一个区块比普通块大一点, 竖直方向最后一块高度也同理
//如: 长为111的图, 水分四块0-26,27-53,54-80,81-110(最后一块水平最大可能是普通块的近两倍)
int iBlockX = x / iOffsetX;
iBlockX = iBlockX == iHBlockNum ? iHBlockNum - 1 : iBlockX; //超出水平块放到最后一块
int iBlockY = y / iOffsetY;
iBlockY = iBlockY == iVBlockNum ? iVBlockNum - 1 : iBlockY;//超出竖直块放到最后一块
int iBlockNum = iBlockY * iHBlockNum + iBlockX; //像素所在区块
arrayBright[iBlockNum] += avg; //三像素平均值
arrayPixelNumber[iBlockNum]++; //第iBlockNum块像素个数加1
}
}
//生成字符串
string strHash = "";
for (int i = 0; i < arrayBright.Length; ++i)
{
//如果不开方就是一个字节,0-255, 两位16进制数,
//调用Convert.ToString(long, 16)会生成ff, ef, a(小数时高位省略了) 等
byte bDigit = (byte)Math.Sqrt(arrayBright[i] / arrayPixelNumber[i]);
strHash += Convert.ToString(bDigit, 16);
}
return strHash;
}
catch
{
return null;
}
}
//================另一个函数, 原理一样, 不是我写的, 像素属于哪一块时判断方法不一样. 这个函数运行时间只要上面函数运行时间的一半, 注释少了些
public string GetPictureHashCodeOld(string strPicPath)
{
string ret = null;
try
{
Bitmap bmp = new Bitmap(strPicPath);
int areanum = 16;
int moietynum = 16;
int h_int = bmp.Height;
int w_int = bmp.Width;
int[,] pic = new int[bmp.Width, bmp.Height];
int max = 0;
int min = 255;
BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, w_int, h_int), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
int stride = bitmapData.Stride;
int nOffset = stride - (int)bitmapData.Width * 3;
IntPtr ptr = bitmapData.Scan0;
int bytes = bitmapData.Stride * bmp.Size.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
bmp.UnlockBits(bitmapData);
int p = 0;
//------------------------
int red, green, blue;
for (int y = 0; y < bitmapData.Height; ++y)
{
for (int x = 0; x < bitmapData.Width; ++x)
{
blue = rgbValues[p];//这里要注意,数据排列按照BGR排列
p++;
green = rgbValues[p];
p++;
red = rgbValues[p];
p++;
byte pointbyte = (byte)((red + green + blue) / 3);//转化成灰度
if (max < pointbyte)
max = pointbyte;
if (min > pointbyte)
min = pointbyte;
pic[x, y] = pointbyte;
}
p += nOffset;
}
int n = max - min + 1;
int m = n / moietynum;
int lost = n % moietynum;
if (lost != 0)
{
m++;
}
for (int i = 0; i < w_int; i++)
{
for (int j = 0; j < h_int; j++)
{
pic[i, j] = (pic[i, j] - min) / m;
}
}
int area = (int)System.Math.Sqrt(areanum);
int w = w_int / area;
int h = h_int / area;
int sum = 0;
int i_s = 0, i_e = w;
int j_s = 0, j_e = h;
int ln = 0;
int rn = 0;
string avgstr = "";
while (rn < area)
{
while (ln < area)
{
sum = 0;
for (int i = i_s; i < i_e; i++)
{
for (int j = j_s; j < j_e; j++)
{
sum += pic[i, j];
}
}
i_s += w;
i_e += w;
int area_avg = sum / (w * h);
avgstr += Convert.ToString(area_avg, 16);
ln++;
}
j_s += h;
j_e += h;
i_s = 0;
i_e = w;
ln = 0;
rn++;
}
ret = avgstr;
return ret;
}
catch
{
ret = null;
return ret;
}
}
两个函数生成的16位字符串码不一样, 比较时用同一个函数. 比较字符是否相差太大/// 返回一个16位hash码(先将图片转成灰度, 再分块得到每个分块的灰度值(0-255), 再开方, 得0-15值. 正好用16进制数表示
/// </summary>
/// <param name="strPicPath">图片路径</param>
/// <returns>16位hash码</returns>
public string GetPictureHashCode(string strPicPath)
{
try
{
//如果传的是字节数组可以用MemoryStream来读取
int iHBlockNum = 4; //图片水平切块数
int iVBlockNum = 4; //图片竖直切块数
long[] arrayBright = new long[iHBlockNum * iVBlockNum]; //用来存放每个块里面所有灰度图像亮度值之和
int[] arrayPixelNumber = new int[iHBlockNum * iVBlockNum]; //用来存放每个块的像素个数
// Create a new bitmap.
Bitmap bitmap = new Bitmap(strPicPath);
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
// Lock the bitmap's bits.
//转成24rgb颜色 24色就是由r g b, 三个颜色, 每个颜色各用一字节(8位)表示亮度
BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
//图片一行象素所占用的字节 本应是3*bitmap.Width的, 但有内存补齐, 一般是4的位数, 实际是大于3*bitmap.Width
int iStride = bmpData.Stride;
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int iBytes = iStride * bitmap.Height;
byte[] rgbValues = new byte[iBytes];
// Copy RGB values into the Array
Marshal.Copy(ptr, rgbValues, 0, iBytes);
// Unlock the bits.
bitmap.UnlockBits(bmpData);
//水平方向每个块的长度 (这里全用整数来除, 得到的值也是整数, bmpData.Width / iOffsetX 的值不会超过 iHBlockNum)
int iOffsetX = bmpData.Width / iHBlockNum;
//竖直方向每个块的长度
int iOffsetY = bmpData.Height / iVBlockNum;
for (int y = 0; y < bmpData.Height; ++y)
{
for (int x = 0; x < bmpData.Width; ++x)
{
//图像(x, y)坐标坐标中第1个像素中rgbValues中的索引位置(这儿x,y是从0开始记的)
//rgbValues每行是扫描宽个字节, 不是bitmap.Width * 3
int iThird = iStride * y + 3 * x;
//计算灰度值
byte avg = (byte)((rgbValues[iThird] + rgbValues[iThird + 1] + rgbValues[iThird + 2]) / 3);
//灰度图
//rgbValues[iThird] = avg;
//rgbValues[iThird + 1] = avg;
//rgbValues[iThird + 2] = avg;
//计算点在哪个区里面 y / iOffsetY 是取除后的整数值0.9算0
//水平最后一个区块比普通块大一点, 竖直方向最后一块高度也同理
//如: 长为111的图, 水分四块0-26,27-53,54-80,81-110(最后一块水平最大可能是普通块的近两倍)
int iBlockX = x / iOffsetX;
iBlockX = iBlockX == iHBlockNum ? iHBlockNum - 1 : iBlockX; //超出水平块放到最后一块
int iBlockY = y / iOffsetY;
iBlockY = iBlockY == iVBlockNum ? iVBlockNum - 1 : iBlockY;//超出竖直块放到最后一块
int iBlockNum = iBlockY * iHBlockNum + iBlockX; //像素所在区块
arrayBright[iBlockNum] += avg; //三像素平均值
arrayPixelNumber[iBlockNum]++; //第iBlockNum块像素个数加1
}
}
//生成字符串
string strHash = "";
for (int i = 0; i < arrayBright.Length; ++i)
{
//如果不开方就是一个字节,0-255, 两位16进制数,
//调用Convert.ToString(long, 16)会生成ff, ef, a(小数时高位省略了) 等
byte bDigit = (byte)Math.Sqrt(arrayBright[i] / arrayPixelNumber[i]);
strHash += Convert.ToString(bDigit, 16);
}
return strHash;
}
catch
{
return null;
}
}
//================另一个函数, 原理一样, 不是我写的, 像素属于哪一块时判断方法不一样. 这个函数运行时间只要上面函数运行时间的一半, 注释少了些
public string GetPictureHashCodeOld(string strPicPath)
{
string ret = null;
try
{
Bitmap bmp = new Bitmap(strPicPath);
int areanum = 16;
int moietynum = 16;
int h_int = bmp.Height;
int w_int = bmp.Width;
int[,] pic = new int[bmp.Width, bmp.Height];
int max = 0;
int min = 255;
BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, w_int, h_int), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
int stride = bitmapData.Stride;
int nOffset = stride - (int)bitmapData.Width * 3;
IntPtr ptr = bitmapData.Scan0;
int bytes = bitmapData.Stride * bmp.Size.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
bmp.UnlockBits(bitmapData);
int p = 0;
//------------------------
int red, green, blue;
for (int y = 0; y < bitmapData.Height; ++y)
{
for (int x = 0; x < bitmapData.Width; ++x)
{
blue = rgbValues[p];//这里要注意,数据排列按照BGR排列
p++;
green = rgbValues[p];
p++;
red = rgbValues[p];
p++;
byte pointbyte = (byte)((red + green + blue) / 3);//转化成灰度
if (max < pointbyte)
max = pointbyte;
if (min > pointbyte)
min = pointbyte;
pic[x, y] = pointbyte;
}
p += nOffset;
}
int n = max - min + 1;
int m = n / moietynum;
int lost = n % moietynum;
if (lost != 0)
{
m++;
}
for (int i = 0; i < w_int; i++)
{
for (int j = 0; j < h_int; j++)
{
pic[i, j] = (pic[i, j] - min) / m;
}
}
int area = (int)System.Math.Sqrt(areanum);
int w = w_int / area;
int h = h_int / area;
int sum = 0;
int i_s = 0, i_e = w;
int j_s = 0, j_e = h;
int ln = 0;
int rn = 0;
string avgstr = "";
while (rn < area)
{
while (ln < area)
{
sum = 0;
for (int i = i_s; i < i_e; i++)
{
for (int j = j_s; j < j_e; j++)
{
sum += pic[i, j];
}
}
i_s += w;
i_e += w;
int area_avg = sum / (w * h);
avgstr += Convert.ToString(area_avg, 16);
ln++;
}
j_s += h;
j_e += h;
i_s = 0;
i_e = w;
ln = 0;
rn++;
}
ret = avgstr;
return ret;
}
catch
{
ret = null;
return ret;
}
}
/// <summary>
/// 根据所给的限定值比较两图是否相同
/// </summary>
/// <param name="strMark1">图一16位字符编码</param>
/// <param name="strMark2">图二16位字符编码</param>
/// <param name="iCharDiff">单个字符最大偏差值</param>
/// <param name="iTotalDiff">最多几个字符偏差</param>
/// <param name="iTotalLimit">字符偏差的允许的总和</param>
/// <returns>相同: true; 不同:false</returns>
public static bool IsSimilarPicture(string strMark1, string strMark2, int iCharDiff, int iTotalDiffNumber, int iTotalLimit)
{
Dictionary<int, int> dictAppearTimes = new Dictionary<int, int>();//记录每个数字出现的次数
if (strMark1.Length != 16 || strMark2.Length != 16)
return false;
int[] b1 = new int[16];
int[] b2 = new int[16];
int iTotal = 0;//偏差总和
//转成10进制整数比较
for (int j = 0; j < 16; ++j)
{
//初始数组
b1[j] = Convert.ToInt32(strMark1[j].ToString(), 16);
b2[j] = Convert.ToInt32(strMark2[j].ToString(), 16);
//统计各个数字出现的次数
//b1数组中第j个数字出现的次数
if (dictAppearTimes.Keys.Contains(b1[j]))
dictAppearTimes[b1[j]]++;
else
dictAppearTimes.Add(b1[j], 1);//头次出现
}
int iMaxCount = dictAppearTimes.Values.Max();
//灰图控制
//将黑色位超过10
if (dictAppearTimes.Keys.Contains(0) && dictAppearTimes[0] > 10)
{
iCharDiff = 1;
iTotalDiffNumber = 1;
iTotalLimit = 1;
}
//如果出现颜色过少
else if (dictAppearTimes.Keys.Count < 4)
{
iCharDiff = 1;
iTotalDiffNumber = 1;
iTotalLimit = 1;
}
//单种颜色超过8位
else if (iMaxCount > 8)
{
iCharDiff = 1;
iTotalDiffNumber = 2;
iTotalLimit = 1;
}
int iDiffNumber = 0; //发生不等的字符个数
for (int i = 0; i < 16; ++i)
{
//比较单个字符是否超过一定值 绝对值超过单个位最大值
int iCompare = Math.Abs(b1[i] - b2[i]);
if (iCompare > iCharDiff)
return false;
//两值不等
if (b1[i] != b2[i])
{
//比较总值是否超过了最大值
iTotal += iCompare;
if (iTotal > iTotalLimit)
return false;
//字符不同的个数
++iDiffNumber;
if (iDiffNumber > iTotalDiffNumber)//超过最大值了
return false;
}
}
return true;
/// 根据所给的限定值比较两图是否相同
/// </summary>
/// <param name="strMark1">图一16位字符编码</param>
/// <param name="strMark2">图二16位字符编码</param>
/// <param name="iCharDiff">单个字符最大偏差值</param>
/// <param name="iTotalDiff">最多几个字符偏差</param>
/// <param name="iTotalLimit">字符偏差的允许的总和</param>
/// <returns>相同: true; 不同:false</returns>
public static bool IsSimilarPicture(string strMark1, string strMark2, int iCharDiff, int iTotalDiffNumber, int iTotalLimit)
{
Dictionary<int, int> dictAppearTimes = new Dictionary<int, int>();//记录每个数字出现的次数
if (strMark1.Length != 16 || strMark2.Length != 16)
return false;
int[] b1 = new int[16];
int[] b2 = new int[16];
int iTotal = 0;//偏差总和
//转成10进制整数比较
for (int j = 0; j < 16; ++j)
{
//初始数组
b1[j] = Convert.ToInt32(strMark1[j].ToString(), 16);
b2[j] = Convert.ToInt32(strMark2[j].ToString(), 16);
//统计各个数字出现的次数
//b1数组中第j个数字出现的次数
if (dictAppearTimes.Keys.Contains(b1[j]))
dictAppearTimes[b1[j]]++;
else
dictAppearTimes.Add(b1[j], 1);//头次出现
}
int iMaxCount = dictAppearTimes.Values.Max();
//灰图控制
//将黑色位超过10
if (dictAppearTimes.Keys.Contains(0) && dictAppearTimes[0] > 10)
{
iCharDiff = 1;
iTotalDiffNumber = 1;
iTotalLimit = 1;
}
//如果出现颜色过少
else if (dictAppearTimes.Keys.Count < 4)
{
iCharDiff = 1;
iTotalDiffNumber = 1;
iTotalLimit = 1;
}
//单种颜色超过8位
else if (iMaxCount > 8)
{
iCharDiff = 1;
iTotalDiffNumber = 2;
iTotalLimit = 1;
}
int iDiffNumber = 0; //发生不等的字符个数
for (int i = 0; i < 16; ++i)
{
//比较单个字符是否超过一定值 绝对值超过单个位最大值
int iCompare = Math.Abs(b1[i] - b2[i]);
if (iCompare > iCharDiff)
return false;
//两值不等
if (b1[i] != b2[i])
{
//比较总值是否超过了最大值
iTotal += iCompare;
if (iTotal > iTotalLimit)
return false;
//字符不同的个数
++iDiffNumber;
if (iDiffNumber > iTotalDiffNumber)//超过最大值了
return false;
}
}
return true;
}