2010-01-16 13 views
10

Ho un ~ 20000 immagini jpg, alcune delle quali sono duplicati. Sfortunatamente, alcuni file sono stati taggati con metadati EXIF, quindi un semplice hash di file non può identificare quello duplicato.Come cancellare solo i dati delle immagini in un file jpg con dotnet?

Sto tentando di creare uno script PowerShell per elaborarli, ma non riesco a trovare alcun modo per estrarre solo i dati bitmap.

System.drawing.bitmap può restituire solo un oggetto bitmap, non byte. Esiste una funzione GetHash(), ma apparentemente agisce sull'intero file.

Come posso cancellare questi file in modo che le informazioni EXIF ​​siano escluse? Preferirei evitare le dipendenze esterne se possibile.

risposta

8

Questa è un'implementazione di funzione avanzata di PowerShell V2.0. È un po 'lungo ma ho verificato che fornisce lo stesso codice hash (generato dai pixel bitmap) sulla stessa immagine ma con metadati e dimensioni di file diversi. Questa è una versione in grado oleodotto che accetta anche i caratteri jolly e percorsi letterali:

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

È possibile caricare il JPEG in uno System.Drawing.Image e utilizzarlo è il metodo GetHashCode

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

Per ottenere i byte si può

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

Il tuo primo approccio non funziona . Restituisce diversi hashcode per la stessa immagine (diversi metadati). Il secondo approccio funziona ed è praticamente ciò che fanno gli altri a diversi livelli di completezza nello script PowerShell. :-) –

0

Tradurre per PowerShell, ho questa -

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

Questo produce lo stesso hash indipendentemente dal file di input, quindi qualcosa non è ancora q è corretto.

5

Ecco uno script PowerShell che produce un hash SHA256 su solo i byte dell'immagine come estratti utilizzando LockBits. Questo dovrebbe produrre un hash univoco per ogni file diverso. Si noti che non ho incluso il codice di iterazione del file, tuttavia dovrebbe essere un'operazione relativamente semplice sostituire il c: \ test.bmp attualmente con un iteratore di directory foreach. La variabile $ finale contiene la stringa hex - ascii dell'hash finale.

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

Forse il codice C equivalente # sarà anche aiutare nella comprensione:

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

0

Questo è un metodo più veloce per salvare su un MemoryStream:

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