2010-01-16 19 views
10

我有一个~20000 JPG图像,其中一些是重复的。不幸的是,某些文件已被标记为EXIF元数据,因此一个简单的文件散列无法识别重复的文件。如何仅使用dotnet散列jpg文件中的图像数据?

我正在尝试创建一个Powershell脚本来处理这些,但无法找到只提取位图数据的方法。

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

如何散列这些文件的方式是EXIF信息被排除?如果可能,我宁愿避免外部依赖。

回答

8

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

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

你的第一个方法不起作用。它为同一图像(不同的元数据)返回不同的哈希码。第二种方法很有效,几乎所有人都在PowerShell脚本中完成不同级别的工作。 :-) – 2010-01-18 20:18:59

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 "" 
     } 
} 

无论输入文件如何,这都会产生相同的哈希值,所以某些东西仍然不是q正确的。

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#代码也将帮助您了解:

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("-", "");     
    } 
} 
+0

BitConverter.ToString() - 很好! – 2010-01-19 03:30:38

0

这是保存到一个MemoryStream更快的方法:

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