代码改变世界

谷歌百度以图搜图 "感知哈希算法" C#简单实现

  狼人:-)  阅读(1669)  评论(1编辑  收藏  举报

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/// <summary>
/// 感知哈希算法
/// </summary>
public class ImageComparer
{      
    /// <summary>
    /// 获取图片的Hashcode
    /// </summary>
    /// <param name="imageName"></param>
    /// <returns></returns>
    public static string GetImageHashCode(string imageName)
    {
        int width = 8;
        int height = 8;
 
        //  第一步
        //  将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节,
        //  只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。
        Bitmap bmp = new Bitmap(Thumb(imageName));
        int[] pixels = new int[width * height];
 
        //  第二步
        //  将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                Color color = bmp.GetPixel(i, j);
                pixels[i * height + j] = RGBToGray(color.ToArgb());
            }
        }
 
        //  第三步
        //  计算所有64个像素的灰度平均值。
        int avgPixel = Average(pixels);
 
        //  第四步
        //  将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。
        int[] comps = new int[width * height];
        for (int i = 0; i < comps.Length; i++)
        {
            if (pixels[i] >= avgPixel)
            {
                comps[i] = 1;
            }
            else
            {
                comps[i] = 0;
            }
        }
 
        //  第五步
        //  将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。
        StringBuilder hashCode = new StringBuilder();
        for (int i = 0; i < comps.Length; i += 4)
        {
            int result = comps[i] * (int)Math.Pow(2, 3) + comps[i + 1] * (int)Math.Pow(2, 2) + comps[i + 2] * (int)Math.Pow(2, 1) + comps[i + 2];
            hashCode.Append(BinaryToHex(result));
        }
        bmp.Dispose();
        return hashCode.ToString();
    }
 
    /// <summary>
    /// 计算"汉明距离"(Hamming distance)。
    /// 如果不相同的数据位不超过5,就说明两张图片很相似;如果大于10,就说明这是两张不同的图片。
    /// </summary>
    /// <param name="sourceHashCode"></param>
    /// <param name="hashCode"></param>
    /// <returns></returns>
    public static int HammingDistance(String sourceHashCode, String hashCode)
    {
        int difference = 0;
        int len = sourceHashCode.Length;
 
        for (int i = 0; i < len; i++)
        {
            if (sourceHashCode[i] != hashCode[i])
            {
                difference++;
            }
        }
        return difference;
    }
 
    /// <summary>
    /// 缩放图片
    /// </summary>
    /// <param name="imageName"></param>
    /// <returns></returns>
    private static Image Thumb(string imageName)
    {
        return Image.FromFile(imageName).GetThumbnailImage(8, 8, () => { return false; }, IntPtr.Zero);
    }
 
    /// <summary>
    /// 转为64级灰度
    /// </summary>
    /// <param name="pixels"></param>
    /// <returns></returns>
    private static int RGBToGray(int pixels)
    {
        int _red = (pixels >> 16) & 0xFF;
        int _green = (pixels >> 8) & 0xFF;
        int _blue = (pixels) & 0xFF;
        return (int)(0.3 * _red + 0.59 * _green + 0.11 * _blue);
    }
 
    /// <summary>
    /// 计算平均值
    /// </summary>
    /// <param name="pixels"></param>
    /// <returns></returns>
    private static int Average(int[] pixels)
    {
        float m = 0;
        for (int i = 0; i < pixels.Length; ++i)
        {
            m += pixels[i];
        }
        m = m / pixels.Length;
        return (int)m;
    }
 
    private static char BinaryToHex(int binary)
    {
        char ch = ' ';
        switch (binary)
        {
            case 0:
                ch = '0';
                break;
            case 1:
                ch = '1';
                break;
            case 2:
                ch = '2';
                break;
            case 3:
                ch = '3';
                break;
            case 4:
                ch = '4';
                break;
            case 5:
                ch = '5';
                break;
            case 6:
                ch = '6';
                break;
            case 7:
                ch = '7';
                break;
            case 8:
                ch = '8';
                break;
            case 9:
                ch = '9';
                break;
            case 10:
                ch = 'a';
                break;
            case 11:
                ch = 'b';
                break;
            case 12:
                ch = 'c';
                break;
            case 13:
                ch = 'd';
                break;
            case 14:
                ch = 'e';
                break;
            case 15:
                ch = 'f';
                break;
            default:
                ch = ' ';
                break;
        }
        return ch;
    }
}

  

 

编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示