4

「JPEG」と「メタデータ」という単語を検索すると、メタデータを操作するための多くの回答が得られます...これは私が望む反対です... ;o)

私が望むように正確に機能する関数を作成しました...(画像が類似していて、メタデータのみが変更されているかどうかにかかわらず、関数は を返しますTrue;少なくとも1つのピクセルが変更された場合、それは を返しますFalse)しかし、私は改善したいと思いますパフォーマンス...

ボトルネックは、bmp.Assign(jpg);

function CompareImages(fnFrom, fnTo: TFileName): Boolean;
var
  j1, j2: TJpegImage;
  b1, b2: TBitmap;
  s1, s2: TMemoryStream;
begin
  Result := False;
sw1.Start;
  j1 := TJpegImage.Create;
  j2 := TJpegImage.Create;
sw1.Stop;
sw2.Start;
  s1 := TMemoryStream.Create;
  s2 := TMemoryStream.Create;
sw2.Stop;
//sw3.Start;
  b1 := TBitmap.Create;
  b2 := TBitmap.Create;
//sw3.Stop;
  try
  sw1.Start;
    j1.LoadFromFile(fnFrom);
    j2.LoadFromFile(fnTo);
  sw1.Stop;

            // the very long part...
            sw3.Start;
              b1.Assign(j1);
              b2.Assign(j2);
            sw3.Stop;


  sw4.Start;
    b1.SaveToStream(s1);
    b2.SaveToStream(s2);
  sw4.Stop;
  sw2.Start;
    s1.Position := 0;
    s2.Position := 0;
  sw2.Stop;
  sw5.Start;
    Result := IsIdenticalStreams(s1, s2);
  sw5.Stop;
  finally
//  sw3.Start;
    b1.Free;
    b2.Free;
//  sw3.Stop;
  sw2.Start;
    s1.Free;
    s2.Free;
  sw2.Stop;
  sw1.Start;
    j1.Free;
    j2.Free;
  sw1.Stop;
  end;
end;

sw1、...、sw5 はTStopWatch です。私は費やした時間を特定するために使用しました。

IsIdenticalStreams はhereから来ています。

を直接比較するTJpegImageと、ストリームが異なります...

それをコーディングするより良い方法はありますか?

よろしく、

W.

アップデート:

コメントから抽出したいくつかのソリューションをテストすると、次のコードで同じパフォーマンスが得られます。

type
  TMyJpeg = class(TJPEGImage)
    public
      function Equals(Graphic: TGraphic): Boolean; override;
  end;

...

function CompareImages(fnFrom, fnTo: TFileName): Boolean;
var
  j1, j2: TMyJpeg;
begin
  sw1.Start;
  Result := False;
  j1 := TMyJpeg.Create;
  j2 := TMyJpeg.Create;
  try
    j1.LoadFromFile(fnFrom);
    j2.LoadFromFile(fnTo);
  Result := j1.Bitmap.Equals(j2.Bitmap);
  finally
    j1.Free;
    j2.Free;
  end;
  sw1.Stop;
end;

ビットマップ変換せずに、ファイルからピクセル データ バイトに直接アクセスする (メタデータ バイトをスキップする) 方法はありますか?

4

1 に答える 1

7

JPEG ファイルはチャンクで構成され、チャンクのタイプはマーカーで識別されます。チャンクの構造 (スタンドアロンの SOI、EOI、RSTn を除く):

chunk type marker (big-endian FFxx)
chunk length (big-endian word)
data (length-2 bytes)

編集: SOS チャンクは、長さではなく、別のマーカーによって制限されます。

メタデータ チャンクは、JFIF タイトルの APP0 (FFE0) マーカーを除いて、APPn マーカー (FFEn) で始まります。

そのため、重要なチャンクのみを読み取って比較し、APPn チャンクと COM チャンクを無視できます (TLama が気づいたように)。

例: 一部の jpeg ファイルの 16 進ビュー: ここに画像の説明を入力

SOI (Start Of Image) マーカー FFD8 (スタンドアロン、長さなし) で始まり、

次に、長さ = 16 バイトの APP0 チャンク (FFE0)、

次に、メタデータ (EXIF データ、NIKON COOLPIX 名など) を含む APP1 チャンク (FFE1) であるため、9053 バイト (23 5D) を無視して、アドレス 2373 で次のチャンク マーカーをチェックできます。

編集:簡単な解析の例:

var
  jp: TMemoryStream;
  Marker, Len: Word;
  Position: Integer;
  PBA: PByteArray;

  procedure ReadLenAndMovePosition;
  begin
    Inc(Position, 2);
    Len := Swap(PWord(@PBA[Position])^);
    Inc(Position, Len);
  end;

begin
  jp := TMemoryStream.Create;
  jp.LoadFromFile('D:\3.jpg');
  Position := 0;
  PBA := jp.Memory;

  while (Position < jp.Size - 1) do begin
    Marker := Swap(PWord(@PBA[Position])^);
    case Marker of
      $FFD8: begin
          Memo1.Lines.Add('Start Of Image');
          Inc(Position, 2);
        end;
      $FFD9: begin
          Memo1.Lines.Add('End Of Image');
          Inc(Position, 2);
        end;
      $FFE0: begin
          ReadLenAndMovePosition;
          Memo1.Lines.Add(Format('JFIF Header Len: %d', [Len]));
        end;
      $FFE1..$FFEF, $FFFE: begin
          ReadLenAndMovePosition;
          Memo1.Lines.Add(Format('APPn or COM Len: %d Ignored', [Len]));
        end;
      $FFDA: begin
          //SOS marker, data stream, ended by another marker except for RSTn
          Memo1.Lines.Add(Format('SOS data stream started at %d', [Position]));
          Inc(Position, 2);
          while Position < jp.Size - 1 do begin
            if PBA[Position] = $FF then
              if not (PBA[Position + 1] in [0, $D0..$D7]) then begin
                Inc(Position, 2);
                Memo1.Lines.Add(Format('SOS data stream ended at %d',
                  [Position]));
                Break;
              end;
            Inc(Position);
          end;
        end;
    else begin
        ReadLenAndMovePosition;
        Memo1.Lines.Add(Format('Marker %x Len: %d Significant', [Marker, Len]));
      end;
    end;
  end;
  jp.Free;
end;
于 2012-05-12T04:06:05.060 に答える