У меня есть ~20000 jpg изображения, некоторые из которых являются дубликатами. К сожалению, некоторые файлы были отмечены с метаданными EXIF, таким образом, простой хеш файла не может определить дублированный.
Я пытаюсь создать сценарий Powershell для обработки их, но не могу найти способ извлечь только растровые данные.
system.drawing.bitmap может только возвратить растровый объект, не байты. Существует GetHash () функция, но он, по-видимому, действует на целый файл.
Как я могу хешировать эти файлы способом, что информация EXIF исключена? Я предпочел бы избегать внешних зависимостей, если это возможно.
Это расширенная реализация функции PowerShell V2.0. Это немного длинновато, но я убедился, что он дает тот же хэш-код (сгенерированный из пикселей растрового изображения) на том же изображении, но с разными метаданными и размерами файлов. Это версия с поддержкой конвейера, которая также принимает подстановочные знаки и буквальные пути:
function Get-BitmapHashCode
{
[CmdletBinding(DefaultParameterSetName="Path")]
param(
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName="Path",
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Path to bitmap file")]
[ValidateNotNullOrEmpty()]
[string[]]
$Path,
[Alias("PSPath")]
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName="LiteralPath",
ValueFromPipelineByPropertyName=$true,
HelpMessage="Path to bitmap file")]
[ValidateNotNullOrEmpty()]
[string[]]
$LiteralPath
)
Begin {
Add-Type -AssemblyName System.Drawing
$sha = new-object System.Security.Cryptography.SHA256Managed
}
Process {
if ($psCmdlet.ParameterSetName -eq "Path")
{
# In -Path case we may need to resolve a wildcarded path
$resolvedPaths = @($Path | Resolve-Path | Convert-Path)
}
else
{
# Must be -LiteralPath
$resolvedPaths = @($LiteralPath | Convert-Path)
}
# Find PInvoke info for each specified path
foreach ($rpath in $resolvedPaths)
{
Write-Verbose "Processing $rpath"
try {
$bmp = new-object System.Drawing.Bitmap $rpath
$stream = new-object System.IO.MemoryStream
$writer = new-object System.IO.BinaryWriter $stream
for ($w = 0; $w -lt $bmp.Width; $w++) {
for ($h = 0; $h -lt $bmp.Height; $h++) {
$pixel = $bmp.GetPixel($w,$h)
$writer.Write($pixel.ToArgb())
}
}
$writer.Flush()
[void]$stream.Seek(0,'Begin')
$hash = $sha.ComputeHash($stream)
[BitConverter]::ToString($hash) -replace '-',''
}
finally {
if ($bmp) { $bmp.Dispose() }
if ($writer) { $writer.Close() }
}
}
}
}
Вот скрипт PowerShell, который производит хеш SHA256 только на байтах изображения, как извлеченные с помощью лосков. Это должно произвести уникальную хэш для каждого файла, который отличается. Обратите внимание, что я не включал код файла итерации файла, однако это должно быть относительно простую задачу для замены в настоящее время Hardcode C: \ Test.BMP с итератором каталогов Foreach. Переменная $ Final содержит шестнадцатеричный - ASCII строка окончательного хеша.
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing.Imaging")
[System.Reflection.Assembly]::LoadWithPartialName("System.Security")
$bmp = [System.Drawing.Bitmap]::FromFile("c:\\test.bmp")
$rect = [System.Drawing.Rectangle]::FromLTRB(0, 0, $bmp.width, $bmp.height)
$lockmode = [System.Drawing.Imaging.ImageLockMode]::ReadOnly
$bmpData = $bmp.LockBits($rect, $lockmode, $bmp.PixelFormat);
$dataPointer = $bmpData.Scan0;
$totalBytes = $bmpData.Stride * $bmp.Height;
$values = New-Object byte[] $totalBytes
[System.Runtime.InteropServices.Marshal]::Copy($dataPointer, $values, 0, $totalBytes);
$bmp.UnlockBits($bmpData);
$sha = new-object System.Security.Cryptography.SHA256Managed
$hash = $sha.ComputeHash($values);
$final = [System.BitConverter]::ToString($hash).Replace("-", "");
Возможно, эквивалентный код C # также поможет вам в понимании:
private static String ImageDataHash(FileInfo imgFile)
{
using (Bitmap bmp = (Bitmap)Bitmap.FromFile(imgFile.FullName))
{
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
IntPtr dataPointer = bmpData.Scan0;
int totalBytes = bmpData.Stride * bmp.Height;
byte[] values = new byte[totalBytes];
System.Runtime.InteropServices.Marshal.Copy(dataPointer, values, 0, totalBytes);
bmp.UnlockBits(bmpData);
SHA256 sha = new SHA256Managed();
byte[] hash = sha.ComputeHash(values);
return BitConverter.ToString(hash).Replace("-", "");
}
}
Вы можете загрузить JPEG в System.Drawing.Image и использовать его метод GetHashCode
using (var image = Image.FromFile("a.jpg"))
return image.GetHashCode();
Для получения байтов можно
using (var image = Image.FromFile("a.jpg"))
using (var output = new MemoryStream())
{
image.Save(output, ImageFormat.Bmp);
return output.ToArray();
}
Перевод к PowerShell, я получаю это -
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$provider = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider
foreach ($location in $args)
{
$files=get-childitem $location | where{$_.Extension -match "jpg|jpeg"}
foreach ($f in $files)
{
$bitmap = New-Object -TypeName System.Drawing.Bitmap -ArgumentList $f.FullName
$stream = New-Object -TypeName System.IO.MemoryStream
$bitmap.Save($stream)
$hashbytes = $provider.ComputeHash($stream.ToArray())
$hashstring = ""
foreach ($byte in $hashbytes)
{$hashstring += $byte.tostring("x2")}
$f.FullName
$hashstring
echo ""
}
}
Это производит тот же хэш независимо от входного файла, поэтому что-то все еще не совсем правильно.