如何使用Dotnet仅对jpg文件中的图像数据进行哈希处理?

10

我有大约20000张jpg图片,其中一些是重复的。不幸的是,一些文件已经被标记了EXIF元数据,所以简单的文件哈希无法识别它们是否是重复的。

我试图创建一个PowerShell脚本来处理这些图片,但找不到只提取位图数据的方法。

System.Drawing.Bitmap只能返回位图对象,而不能返回字节。虽然有一个GetHash()函数,但它显然作用于整个文件。

如何对这些文件进行哈希处理以排除EXIF信息?如果可能的话,我更愿意避免使用外部依赖项。

5个回答

10

这是一个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() }
            }
        }  
    }
}

13年后,你救了我的一天!谢谢。 - undefined

5
您可以将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();
}

2
你的第一种方法不起作用。它为相同的图像(不同的元数据)返回不同的哈希码。第二种方法有效,并且在PowerShell脚本中几乎是每个人都在以不同程度的完整性执行的。 :-) - Keith Hill

5
下面是一个 PowerShell 脚本,它仅针对使用 LockBits 提取的图像字节产生 SHA256 哈希。这应该为每个文件生成不同的唯一哈希值。请注意,我没有包含文件迭代代码,但是用 foreach 目录迭代器替换当前硬编码的 c:\test.bmp 应该是相对简单的任务。变量 $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#代码也有助于您的理解:
```csharp //此处为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("-", "");                
    }
}

BitConverter.ToString() - 很棒! - Keith Hill

0

翻译成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

这是一种更快的将数据保存到内存流的方法:

$ms = New-Object System.IO.MemoryStream
$bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Bmp)
[void]$ms.Seek(0,'Begin')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接