2010-01-16 16 views
10

Tengo ~ 20000 jpg de imágenes, algunas de las cuales son duplicados. Desafortunadamente, algunos archivos han sido etiquetados con metadatos EXIF, por lo que un archivo hash simple no puede identificar el duplicado.¿Cómo hash solo datos de imagen en un archivo jpg con dotnet?

Estoy intentando crear un script de Powershell para procesar estos, pero no puedo encontrar la manera de extraer solo los datos del mapa de bits.

El system.drawing.bitmap solo puede devolver un objeto de mapa de bits, no bytes. Hay una función GetHash(), pero aparentemente actúa en todo el archivo.

¿Cómo puedo copiar estos archivos de forma que se excluya la información EXIF? Prefiero evitar dependencias externas si es posible.

Respuesta

8

Esta es una implementación avanzada de la función PowerShell V2.0. Es un poco largo, pero he verificado que proporciona el mismo código hash (generado a partir de los píxeles del mapa de bits) en la misma imagen, pero con diferentes metadatos y tamaños de archivo. Esta es una versión capaz oleoducto que también acepta comodines y caminos literales:

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

Puede cargar el JPEG en un System.Drawing.Image y utilizarla es el método GetHashCode

using (var image = Image.FromFile("a.jpg")) 
    return image.GetHashCode(); 

Para obtener los bytes que puede

using (var image = Image.FromFile("a.jpg")) 
using (var output = new MemoryStream()) 
{ 
    image.Save(output, ImageFormat.Bmp); 
    return output.ToArray(); 
} 
+1

Su primer enfoque no funciona . Devuelve diferentes códigos hash para la misma imagen (metadatos diferentes). El segundo enfoque funciona y es más o menos lo que hacen todos los demás a los distintos niveles de integridad en el script de PowerShell. :-) –

0

se traduce en PowerShell, me sale esto -

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

Esto produce el mismo hash independientemente del archivo de entrada, por lo que algo todavía no está q uite correcto.

5

Aquí hay un script de PowerShell que produce un hash SHA256 sólo en los bytes de la imagen utilizando como LockBits extraídos. Esto debería producir un hash único para cada archivo que sea diferente. Tenga en cuenta que no incluí el código iterativo del archivo, sin embargo, debería ser una tarea relativamente simple reemplazar el código duro actual c: \ test.bmp con un iterador de directorio foreach. La variable $ final contiene la cadena hexadecimal - ascii del hash final.

[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("-", ""); 

Tal vez el código C# equivalentes también le ayudan a comprender:

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() - ¡Bien! –

0

Este es un método más rápido para salvar a un MemoryStream:

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