Skip to content

Instantly share code, notes, and snippets.

@ksasao
Last active January 17, 2022 02:41
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save ksasao/e625d590801dce98c5e0 to your computer and use it in GitHub Desktop.
Save ksasao/e625d590801dce98c5e0 to your computer and use it in GitHub Desktop.
C#による高速画像一致検索クラス。フル版は https://github.com/ksasao/Gochiusearch にあります。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace ImageSearch
{
public class ImageVector
{
string _basePath;
public ImageVector(string basePath)
{
_basePath = basePath;
}
/// <summary>
/// 画像ファイルから類似画像が近い値を持つようなハッシュ値を計算します
/// </summary>
/// <param name="filename">画像ファイル</param>
/// <returns>ハッシュ値</returns>
public ulong GetVector(string filename)
{
// dHash (difference hash) を計算
// http://www.hackerfactor.com/blog/?/archives/529-Kind-of-Like-That.html
// 入力した画像を 9x8 の領域にスケールする
Bitmap bmpVector = new Bitmap(9, 8);
using (Graphics g = Graphics.FromImage(bmpVector))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
Bitmap bmp = new Bitmap(filename);
g.DrawImage(bmp,
new Rectangle(0, 0, bmpVector.Width, bmpVector.Height),
new Rectangle(0, 0, bmp.Width, bmp.Height), GraphicsUnit.Pixel);
bmp.Dispose();
}
byte[] data = BitmapToByteArray(bmpVector);
bmpVector.Dispose();
// モノクロ化
int[] mono = new int[data.Length / 4];
for (int i = 0; i < mono.Length; i++)
{
int a = (29 * data[i * 4] + 150 * data[i * 4 + 1] + 77 * data[i * 4 + 2]) >> 8;
mono[i] = a;
}
// 横方向で隣接するピクセル間の輝度差をビットベクトルとする
ulong result = 0;
int p = 0;
for (int y = 0; y < 8; y++)
{
for (int x = 0; x < 8; x++)
{
result = (result << 1) | (mono[p] > mono[p + 1] ? (uint)1 : 0);
p++;
}
p++;
}
return result;
}
/// <summary>
/// 指定した画像ファイルを画像のハッシュ値に相当するフォルダにコピーします
/// </summary>
/// <param name="target">画像ファイルのパス</param>
/// <returns>格納先パス</returns>
public string AddImage(string target)
{
string path = GetImageDirectory(target);
Directory.CreateDirectory(path);
string result = path + Path.GetFileName(target);
File.Copy(target, result, true);
return result;
}
/// <summary>
/// ハッシュ値からフォルダ名を返します
/// </summary>
/// <param name="vec">画像のハッシュ値</param>
/// <returns>ハッシュ値に相当するフォルダ</returns>
public string GetImageDirectory(ulong vec)
{
string path = _basePath + GetPath(vec);
return path;
}
/// <summary>
/// ハッシュ値からパス名を返します
/// </summary>
/// <param name="vec"></param>
/// <returns></returns>
private string GetPath(ulong vec)
{
// 上位から8ビットずつ取り出しPath名とする
int i0 = (int)(vec & 0xFF);
vec >>= 8;
int i8 = (int)(vec & 0xFF);
vec >>= 8;
int i16 = (int)(vec & 0xFF);
vec >>= 8;
int i24 = (int)(vec & 0xFF);
vec >>= 8;
int i32 = (int)(vec & 0xFF);
vec >>= 8;
int i40 = (int)(vec & 0xFF);
vec >>= 8;
int i48 = (int)(vec & 0xFF);
vec >>= 8;
int i56 = (int)(vec & 0xFF);
return string.Format(@"{0:X2}\{1:X2}\{2:X2}\{3:X2}\{4:X2}\{5:X2}\{6:X2}\{7:X2}\",
i56, i48, i40, i32, i24, i16, i8, i0);
}
private string GetImageDirectory(string target)
{
ulong vec = GetVector(target);
string path = _basePath + GetPath(vec);
return path;
}
/// <summary>
/// Bitmapをbyte[]に変換する
/// </summary>
/// <param name="bitmap">変換元のBitmap</param>
/// <returns>1 pixel = 4 byte (+3:A, +2:R, +1:G, +0:B) に変換したbyte配列</returns>
private byte[] BitmapToByteArray(Bitmap bmp)
{
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
// Bitmapの先頭アドレスを取得
IntPtr ptr = bmpData.Scan0;
// 32bppArgbフォーマットで値を格納
int bytes = bmp.Width * bmp.Height * 4;
byte[] rgbValues = new byte[bytes];
// Bitmapをbyte[]へコピー
Marshal.Copy(ptr, rgbValues, 0, bytes);
bmp.UnlockBits(bmpData);
return rgbValues;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment