What is the fastest solution for comparing JPEG images? (ignoring metadata, only "pixels")

When I search for the words “JPEG” and “metadata”, I have many answers to manipulate the metadata ... and vice versa, I want ...; o)

I wrote a function that works exactly the way I want ... (if the images are similar and only the metadata is changed or not, the function returns True ; if at least one pixel changes, it returns False ), but I would like to improve performance ...

The bottleneck is 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 , I used to determine the time.

IsIdenticalStreams comes from here .

If I directly compare TJpegImage , the streams are different ...

What is the best way to code this?

Hi,

Sh.

Update:

Checking out some solutions, extract from the comments, I have the same performance with this code:

 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; 

Is there any way to directly access bytes of pixel data from a file (bypassing metadata bytes) without converting a bitmap?

+4
source share
1 answer

A JPEG file consists of pieces whose types are identified by bullets. Piece structure (excluding standalone SOI, EOI, RSTn):

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

Edit: SOS block is limited to a different marker, not length.

Metadata chunks begin with the APPn marker (FFEn), with the exception of the APP0 marker (FFE0) with the JFIF header.

Thus, we can read and compare only significant fragments and ignore the APPn fragments and the COM fragment (as Tlama noted).

Example: hexadecimal representation of some jpeg file: enter image description here

It starts with the SOI (Image Start) marker FFD8 (stand-alone, no length),

then the part of APP0 (FFE0) with a length = 16 bytes,

then APP1 chunk (FFE1), which contains metadata (EXIF data, NIKON COOLPIX username, etc.), so we can ignore 9053 bytes (23 5D) and check the next block marker at 2373, etc.

Edit: A simple parsing example:

 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; 
+7
source

Source: https://habr.com/ru/post/1412092/


All Articles