当我搜索单词“JPEG”和“元数据”时,我有很多答案来操纵元数据……这是我想要的相反……; o) 我写了一个函数,它完全像我想要的那样…(如果图像相似,只有元数据改变与否,函数返回
我写了一个函数,它完全像我想要的那样…(如果图像相似,只有元数据改变与否,函数返回True;如果至少有一个像素改变,则返回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;
有没有直接访问文件中的像素数据字节(跳过元数据字节)而无需位图转换的方法?
JPEG文件由块组成,这些块由标记标识.块的结构(独立的SOI,EOI,RSTn除外):chunk type marker (big-endian FFxx) chunk length (big-endian word) data (length-2 bytes)
编辑:SOS块受另一个标记的限制,而不是长度.
元数据块以APPn标记(FFEn)开始,但具有JFIF标题的APP0(FFE0)标记除外.
所以我们只能读取和比较重要的块并忽略APPn块和COM块(如TLama注意到的).
示例:某些jpeg文件的十六进制视图:
它以SOI(图像开始)标记FFD8(独立,无长度)开始,
然后APP0块(FFE0)长度= 16字节,
然后APP1块(FFE1),其中包含元数据(EXIF数据,NIKON COOLPIX名称等),因此我们可以忽略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;