Last active
January 17, 2022 02:41
-
-
Save ksasao/e625d590801dce98c5e0 to your computer and use it in GitHub Desktop.
C#による高速画像一致検索クラス。フル版は https://github.com/ksasao/Gochiusearch にあります。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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