Как хешировать только данные изображения в jpg файле с dotnet?

У меня есть ~20000 jpg изображения, некоторые из которых являются дубликатами. К сожалению, некоторые файлы были отмечены с метаданными EXIF, таким образом, простой хеш файла не может определить дублированный.

Я пытаюсь создать сценарий Powershell для обработки их, но не могу найти способ извлечь только растровые данные.

system.drawing.bitmap может только возвратить растровый объект, не байты. Существует GetHash () функция, но он, по-видимому, действует на целый файл.

Как я могу хешировать эти файлы способом, что информация EXIF исключена? Я предпочел бы избегать внешних зависимостей, если это возможно.

10
задан JSacksteder 16 January 2010 в 16:20
поделиться

4 ответа

Это расширенная реализация функции 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() }
            }
        }  
    }
}
9
ответ дан 3 December 2019 в 20:41
поделиться

Вот скрипт 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("-", "");                
    }
}
5
ответ дан 3 December 2019 в 20:41
поделиться

Вы можете загрузить 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();
}
4
ответ дан 3 December 2019 в 20:41
поделиться

Перевод к 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 ""
        }
} 

Это производит тот же хэш независимо от входного файла, поэтому что-то все еще не совсем правильно.

0
ответ дан 3 December 2019 в 20:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: