Fast way of comparing 2 bitmaps lazarus / 39-Graphics ---------------------------------------------------------------------- Pi · 2022-07-24 11:59 Do you know one of the fastest ways of comparing 2 bitmaps colour- wise? eg. in this case I have 2 folders of bmp sequences from videos, and I want to go through the bitmaps in folder 1 looking for the closest bitmap to it in folder 2 (eg. the one that looks the same/nearly the same). There's 642 bmps in folder 1 and 814 bmps in folder 2. I'm currently loading each image into an image control (so 2 image controls on a form) and doing a for loop through the pixels and looking at the colour eg.  Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i col_1:=image1.Picture.Bitmap.Canvas.Pixels[x,y]; Then using getrvalue, getgvalue etc. to find the differences (using abs to make it a positive number) between the rgb values at each pixel position, totalling them up for an image comparison and storing the image nr with the lowest difference. The images are 1080x1920 and it takes a long time to process every pixel (eg. if I compared every pixel of every image in folder 1 with every image in folder 2 I think it would take years to complete). I changed it so it skips pixels (so in the for loop if it's not divisible by a certain number then go back to the loop. That will reduce precision but it still isn't fast enough with it just looking at every 100 pixels in the x & y. Is there a faster way? Can someone please give me a hint how to do this using scanline if that will be a lot faster? Is using scanline the fastest way? It might be that comparing the grey-scale values might be good enough if comparing all the red, green and blue values will be too slow. ---------------------------------------------------------------------- winni · 2022-07-24 12:23 Hi! Install the BGRAbitmapPack available through the Online Package Manager. This code creates copies of image1 and image2. Then it compares the raw data of the two bitmaps and the difference is available in  dred,dgreen,dblue and dalpha. There you have to put your code for the difference. BGRAbitmap is not optimized for speed, but it is explicit faster than the TCanvas which is realy slow. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses ....,BGRABitmap, BGRABitmapTypes;..... procedure TForm1.Button1Click(Sender: TObject);var bmp1,bmp2 : TBGRAbitmap;    p1,p2:PBGRAPixel;    i : integer;    dred,dgreen,dblue, dalpha : Integer;begin  bmp1 := TBGRAbitmap.create(image1.Picture.Bitmap);  bmp2 := TBGRAbitmap.create(image2.Picture.Bitmap);  if bmp1.NbPixels <> bmp2.NbPixels then    begin    showMessage ('Different Dimensions!');    exit;    end;  p1 := bmp1.data;  p2 := bmp2.data;  for i := 0 to bmp1.NbPixels-1 do     begin     dred := p2^.red -p1^.red;     dgreen := p2^.green -p1^.red;     dblue := p2^.blue -p1^.blue;     dalpha := p2^.alpha -p1^.alpha;      .....     inc(p1);     inc(p2)     end;  bmp1.free;  bmp2.free;end; Winni ---------------------------------------------------------------------- Pi · 2022-07-24 12:33 Thanks a lot. I'm installing that now and will try that code. edit: Thanks a lot. I've used and edited the code (after doing the online install and adding it to one of the package things) and it seems to be working now, faster than before. Thank you. ---------------------------------------------------------------------- flowCRANE · 2022-07-25 22:37 If you want to iterate very fast through bitmap pixels, just use TBitmap.ScanLine and compare pixel by pixel using pointers. ---------------------------------------------------------------------- dje · 2022-07-26 01:40 You could automate the imagemagick command line tools if you don't want to code it from scratch. https://legacy.imagemagick.org/Usage/compare/#compare https://imagemagick.org/script/compare.php https://legacy.imagemagick.org/discourse-server/viewtopic.php?t=29587 https://legacy.imagemagick.org/discourse-server/viewtopic.php?t=30175 http://www.fmwconcepts.com/imagemagick/similar/index.php edit: There is a ton of information if you google "find similar images using imagemagick" It looks like imagemagick handles all kinds of variations in images, including size, color, images in images, etc. http://www.imagemagick.org/discourse-server/viewtopic.php?t=32522 ---------------------------------------------------------------------- Pi · 2022-07-26 11:36 Quote from: furious programming on July 26, 2022, 12:37:01 amIf you want to iterate very fast through bitmap pixels, just use TBitmap.ScanLine and compare pixel by pixel using pointers. Thanks. I'd read about scanline, but couldn't see any example code where it did something a bit like it. Are there any links/examples where it does something like this please (where each pixel value is checked/accessed using pointers)? The code given by winni worked a lot faster than my code, but when comparing each of 642 images with 814 still took quite a long time when working with 1080x1920 images. Though I thought in future I could render the images at half the width and height (just for the comparisons) and it should still be able to find the closest images and should be quite a bit faster (4x as fast maybe). Quote from: derek.john.evans on July 26, 2022, 03:40:30 amYou could automate the imagemagick command line tools if you don't want to code it from scratch. Thanks. I'm checking out those links too. The link to the script (http://www.fmwconcepts.com/imagemagick/similar/index.php), said I'd need to contact him if I needed it for commercial use though. I'm not sure how complex it would be to do what I want in that with scripting since what I was using it for was finding the image (and it's frame nr) that was the closest match for a particular frame in the folder with about 600 images and then copying another file from another folder with that frame number to a new directory with the frame number that I was checking against. eg. a video of 814 frames had been rotoscoped, a new source video of 642 frames was given that seemed to only contain frames from the 814 frame video but with cuts/edits & retimings, so that copied the rotoscoped frames (png sequence with alpha in a different directory) based on the image frames of the 814 bmp sequence that were the closest match. So that helped having things work in functions in Lazarus to do that (eg. since you knew the frame/image nrs (of the frames you were checking and the ones you were checking against, the difference amount from each comparison as an integer, the frame nr of the closest match etc.). ---------------------------------------------------------------------- SymbolicFrank · 2022-07-26 13:29 Load them as textures onto your GPU and write a shader that does the comparison. ---------------------------------------------------------------------- flowCRANE · 2022-07-26 13:47 Quote from: Pi on July 26, 2022, 01:36:06 pmThanks. I'd read about scanline, but couldn't see any example code where it did something a bit like it. Something like the following should be a valid pattern to iterate through bitmap pixels (both bitmaps must have the same dimensions): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function CompareBitmaps(ABitmapA, ABitmapB: TBitmap): Integer;type  TBitmapPixel = record B, G, R: UInt8 end;  PBitmapPixel = ^TBitmapPixel;var  PixelA, PixelB: PBitmapPixel;  LineIndex, PixelIndex: Integer;begin  Result := 0;   for LineIndex := 0 to ABitmapA.Height - 1 do  begin    PixelA := ABitmapA.ScanLine[LineIndex];    PixelB := ABitmapB.ScanLine[LineIndex];     for PixelIndex := 0 to ABitmapA.Width - 1 do    begin      // compare pixel channels       PixelA += 1;      PixelB += 1;    end;  end;end; Now, you can add any code to compare pixels in the place of the comment. For example: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i // count how many channels differResult += Ord(PixelA^.R <> PixelB^.R);Result += Ord(PixelA^.G <> PixelB^.G);Result += Ord(PixelA^.B <> PixelB^.B); So the result of the code above will be the sum of channels that differ. If you want to calculate how many pixels differ, you can increase the Result once per pixel: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i // count how many pixels differResult += Ord((PixelA^.R <> PixelB^.R) or (PixelA^.G <> PixelB^.G) or (PixelA^.B <> PixelB^.B)); // or just in the form of conditionif (PixelA^.R <> PixelB^.R) or (PixelA^.G <> PixelB^.G) or (PixelA^.B <> PixelB^.B) then  Result += 1; If you have 32-bit image (bitmap, PNG or any other with ScanLine), you can compare all pixel channels in one iteration of the loop. I can't remember but the 24-bit bitmap most likely also stores pixels in the internal buffer as 32-bit blocks, so you can also use integer-to- integer comparison. In such a case, the entire code will look like this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function CompareBitmaps(ABitmapA, ABitmapB: TBitmap): Integer;var  PixelA, PixelB: PUInt32;  LineIndex, PixelIndex: Integer;begin  Result := 0;   for LineIndex := 0 to ABitmapA.Height - 1 do  begin    PixelA := ABitmapA.ScanLine[LineIndex];    PixelB := ABitmapB.ScanLine[LineIndex];     for PixelIndex := 0 to ABitmapA.Width - 1 do    begin      // count how many pixels differ    Result += Ord(PixelA^ <> PixelB^);       PixelA += 1;      PixelB += 1;    end;  end;end; Even faster way will be to use pointers to current and last pixel in the row, instead of using second nested for-loop (this is the fastest way of iteration): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function CompareBitmaps(ABitmapA, ABitmapB: TBitmap): Integer;var  PixelA, PixelB, PixelStop: PUInt32;  LineIndex: Integer;begin  Result := 0;   for LineIndex := 0 to ABitmapA.Height - 1 do  begin    PixelA    := ABitmapA.ScanLine[LineIndex];    PixelB    := ABitmapB.ScanLine[LineIndex];    PixelStop := PixelA + ABitmapA.Width;     while PixelA < PixelStop do    begin      // count how many pixels differ      Result += Ord(PixelA^ <> PixelB^);       PixelA += 1;      PixelB += 1;    end;  end;end; Not tested but, I used this approach many times in the past. ---------------------------------------------------------------------- Pi · 2022-07-26 13:57 Quote from: furious programming on July 26, 2022, 03:47:14 pmSomething like the following should be a valid pattern to iterate through bitmap pixels (both bitmaps must have the same dimensions): Thanks a lot. I'll try it that way based on your code. Quote from: SymbolicFrank on July 26, 2022, 03:29:04 pmLoad them as textures onto your GPU and write a shader that does the comparison. Thanks. I'll look into that but I've never written shader so that way might be too complex. ---------------------------------------------------------------------- marcov · 2022-07-26 14:01 Or just use AVX2. This calculates sum ( abs(r1-r2)+abs(b1-b2)+abs(g1-g2)) and stores it (saturated) in a one byte bitmap. It was made as a quick demo to get a feel how much this could be optimized for system dimensioning purposes and has some limitations: topdown onlyNo alignment or rest bytes usage, use image widths that are multiples of 32 meant for win64, though changing for Linux 64-bit would be doable not heavily testedCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i // very rough + simplified colour distance (rgba to 8-bit).// useless since real camera data is 8-bit, but made because simulating the 32-bit// images was too slow in plain pascal.{$ifdef iacamarker}{$info iacamarker on in production code!}{$endif} const      splitsh6 :  array[0..31] of byte = (  $00,$04,$08,$0C,$01,$05,$09,$0d,                                          $02,$06,$0A,$0E,$03,$07,$0B,$0F,                                          $00,$04,$08,$0C,$01,$05,$09,$0d,                                            $02,$06,$0A,$0E,$03,$07,$0B,$0F);      permto8 :  array[0..31] of byte = (   $00,$00,$00,$00,$04,$00,$00,$00,                                          $01,$00,$00,$00,$05,$00,$00,$00,                                          $02,$00,$00,$00,$06,$00,$00,$00,                                          $03,$00,$00,$00,$07,$00,$00,$00);  // http://threadlocalmutex.com/?p=8// reduce Rgba andmask to 8bit for distance image, splitchannel variant// stackframe comes from VS code. (not related to the body of the code)procedure asmColourDistance(prgba1,prgba2 : pbyte;pdest8:pbyte;countinner,countouter: integer);assembler; nostackframe;// src= rcx src2, rdx, pdest8 r8 countinner=r9,countouter=stackasm  mov   rax, rsp        mov     QWORD PTR [rax+8], rbx        mov     QWORD PTR [rax+16], rdi        push  rbp        mov     rdi,[rsp+$30]         sub     rsp, 128                              // 00000080H        vmovaps XMMWORD PTR [rax-24], xmm6        vmovaps XMMWORD PTR [rax-40], xmm7        vmovaps XMMWORD PTR [rax-56], xmm8        vmovaps XMMWORD PTR [rax-72], xmm9        vmovaps XMMWORD PTR [rax-88], xmm10        vmovaps XMMWORD PTR [rax-104], xmm11        lea     rbp, QWORD PTR [rax-104]        and   rbp, -32                                // ffffffffffffffe0H         mov     rax,rdi        // pixels naar slagen.       shr     r9,5      mov      r11d,$7F7F7F7F       movd     xmm0,r11d       vpbroadcastd ymm11,xmm0        // in FPC code, loads of constants should now be 32-byte aligned (constmin)       vmovdqa  ymm9, [rip+permto8]       vmovdqa  ymm10,[rip+splitsh6] @louter:        mov     r11,r9align 16@linner:{$ifdef iacamarker}        mov ebx, 111          // Start marker bytes       db $64, $67, $90   // Start marker bytes{$endif}      //first load        vmovdqa  ymm0,[rcx]        vpsrlw  ymm0,ymm0,1        vpand   ymm0,ymm0,ymm11        vmovdqa  ymm1,[rdx]        vpsrlw  ymm1,ymm1,1        vpand   ymm1,ymm1,ymm11        vpsubsb ymm1,ymm1,ymm0        vpabsb  ymm0,ymm1        // arrange dwords together.   // absolute difference.        vpshufb ymm0,ymm0,ymm10        // gather qwords together.         vpermd  ymm4,ymm9,ymm0         // ymm4..7 are channels to store.        // ymm4 is already loaded.         // process first load        vpermq  ymm5,ymm4,1+2*4+3*16+0*64        vpermq  ymm6,ymm4,2+1*4+3*16+0*64      vpermq  ymm7,ymm4,3+1*4+2*16+0*64         // second load        vmovdqa  ymm0,[rcx+32]        vpsrlw   ymm0,ymm0,1        vpand    ymm0,ymm0,ymm11        vmovdqa  ymm1,[rdx+32]        vpsrlw   ymm1,ymm1,1        vpand    ymm1,ymm1,ymm11        vpsubsb  ymm1,ymm1,ymm0        vpabsb   ymm0,ymm1        // arrange dwords together.        vpshufb  ymm0,ymm0,ymm10        // gather qwords together.        vpermd   ymm2,ymm9,ymm0         //process second load.        vpermq   ymm1,ymm2,1+0*4+3*16+2*64        vpblendd ymm4,ymm4,ymm1,4+8        vpblendd ymm5,ymm5,ymm2,4+8        vpermq   ymm0,ymm2,3+2*4+1*16+0*64        vpblendd ymm6,ymm6,ymm0,4+8        vpermq   ymm2,ymm2,1+3*4+2*16+0*64        vpblendd ymm7,ymm7,ymm2,4+8         // third load        vmovdqa  ymm0,[rcx+64]        vpsrlw  ymm0,ymm0,1        vpand   ymm0,ymm0,ymm11        vmovdqa  ymm1,[rdx+64]        vpsrlw  ymm1,ymm1,1        vpand   ymm1,ymm1,ymm11        vpsubsb ymm1,ymm1,ymm0        vpabsb  ymm0,ymm1        // arrange dwords together.        vpshufb ymm0,ymm0,ymm10        // gather qwords together.        vpermd  ymm2,ymm9,ymm0         //process third load.        vpermq   ymm1,ymm2,1+2*4+0*16+3*64        vpblendd ymm4,ymm4,ymm1,16+32        vpermq   ymm0,ymm2,3+2*4+1*16+0*64        vpblendd ymm5,ymm5,ymm0,16+32        vpblendd ymm6,ymm6,ymm2,16+32      vpermq   ymm1,ymm2,2+1*4+3*16+0*64        vpblendd ymm7,ymm7,ymm1,16+32         // fourth load        vmovdqa  ymm0,[rcx+96]        vpsrlw  ymm0,ymm0,1        vpand   ymm0,ymm0,ymm11        vmovdqa  ymm1,[rdx+96]        vpsrlw  ymm1,ymm1,1        vpand   ymm1,ymm1,ymm11        vpsubsb ymm1,ymm1,ymm0        vpabsb  ymm0,ymm1        // arrange dwords together.        vpshufb ymm0,ymm0,ymm10        // gather qwords together.        vpermd  ymm2,ymm9,ymm0         //process fourth load.        vpermq   ymm1,ymm2,1+2*4+3*16+0*64        vpblendd ymm4,ymm4,ymm1,64+128        vpermq   ymm0,ymm2,3+2*4+0*16+1*64      vpblendd ymm5,ymm5,ymm0,64+128        vpermq   ymm3,ymm2,1+3*4+0*16+2*64        vpblendd ymm6,ymm6,ymm3,64+128      vpblendd ymm7,ymm7,ymm2,64+128         // we now have 3 regs with abs differences per colour channel. (ignoring channel a)        // we could do something more interesting, but for now, just add them      // together.        vpaddsb  ymm4,ymm4,ymm5        vpaddsb  ymm6,ymm6,ymm7        vpaddsb  ymm4,ymm4,ymm6        vmovdqa [r8],ymm4{$ifdef iacamarker}    mov ebx, 222          // End marker bytes    db $64, $67, $90   // End marker bytes{$endif}         add   rdx,128        add   rcx,128        add   r8,32        dec   r11      jne   @linner        dec   rax        jne   @louter        vzeroupper        lea     r11, QWORD PTR [rsp+128]        mov     rbx, QWORD PTR [r11+16]        mov     rdi, QWORD PTR [r11+24]        vmovaps xmm6, XMMWORD PTR [r11-16]        vmovaps xmm7, XMMWORD PTR [r11-32]        vmovaps xmm8, XMMWORD PTR [r11-48]        vmovaps xmm9, XMMWORD PTR [r11-64]        vmovaps xmm10, XMMWORD PTR [r11-80]        vmovaps xmm11, XMMWORD PTR [r11-96]        mov     rsp, r11      pop     rbp        ret     0end; ---------------------------------------------------------------------- flowCRANE · 2022-07-26 14:07 And of course it would be good to use all available CPU cores to do this job. ---------------------------------------------------------------------- Pi · 2022-07-26 15:39 Quote from: marcov on July 26, 2022, 04:01:26 pmOr just use AVX2. This calculates sum ( abs(r1-r2)+abs(b1-b2)+abs(g1-g2)) and stores it (saturated) in a one byte bitmap... Thanks a lot. I'll try that too. The original images I was checking were 1080 wide x 1920 high (so not 32 pixel multiple)  but I'll check it with 1920 width images. ---------------------------------------------------------------------- dseligo · 2022-07-27 00:01 Quote from: furious programming on July 26, 2022, 03:47:14 pmCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i PixelA += 1; += Ehhhhhh ---------------------------------------------------------------------- Thausand · 2022-07-27 00:15 Quote from: dseligo on July 27, 2022, 02:01:12 amQuote from: furious programming on July 26, 2022, 03:47:14 pmCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i PixelA += 1; += Ehhhhhh documentation read:  https://www.freepascal.org/docs- html/prog/progsu10.html ---------------------------------------------------------------------- lainz · 2022-07-27 00:16 Quote from: furious programming on July 26, 2022, 04:07:27 pmAnd of course it would be good to use all available CPU cores to do this job. An example of multiple thread filter is here: https://github.com/bgrabitmap/multithreadedfilter/blob/master/umultith readfilter.pas ---------------------------------------------------------------------- dje · 2022-07-27 05:59 findimagedupes looks interesting. Its a debian perl script that seems to automate ImageMagick. Source code: https://github.com/jhnc/findimagedupes https://sources.debian.org/src/findimagedupes/2.18-7/findimagedupes/ Man Page: https://manpages.ubuntu.com/manpages/bionic/man1/findimagedupes.1p.htm l The algorithm is described as: QuoteTo calculate an image fingerprint:  1. Read image.  2. Resample to 160x160 to standardize size.  3. Grayscale by reducing saturation.  4. Blur a lot to get rid of noise.  5. Normalize to spread out intensity as much as possible.  6. Equalize to make image as contrasty as possible.  7. Resample again down to 16x16.  8. Reduce to 1bpp.  9. The fingerprint is this raw image data. To compare two images for similarity:  1. Take fingerprint pairs and xor them.  2. Compute the percentage of 1 bits in the result.  3. If percentage exceeds threshold, declare files to be similar. Maybe run a test on your images to see what happens. If the results are successful, then you have place to start from. ie: What needs to be re-coded in Pascal to achieve the same results. ---------------------------------------------------------------------- dje · 2022-07-27 06:17 Sorry, another piece of info: PascalMagick? Never used it, unsure if it works, but if it does, you should be able to convert the findimagedupes algorithm to Pascal. https://wiki.freepascal.org/PascalMagick https://sourceforge.net/projects/lazarus- ccr/files/PascalMagick/PascalMagick%200.4/ https://blog.dummzeuch.de/2019/10/22/pascalmagick-magickdistortimage- in-delphi/ Just some thoughts :-) ---------------------------------------------------------------------- Pi · 2022-07-27 15:12 Quote from: derek.john.evans on July 27, 2022, 08:17:21 amSorry, another piece of info: PascalMagick? Never used it, unsure if it works, but if it does, you should be able to convert the findimagedupes algorithm to Pascal... Thanks. I'm checking that too. I was thinking that finding the most similar image from a directory may be better for what I described above but I can also use a dupe finder (eg. for finding duplicate frames in a video and working out the real frame rate). In the "findimagedupes.1p" link they resample the images down to 16x16 pixels I think which I thought may be a bit low if you're comparing 1080p or higher video frame bmps. I'll check the code though thanks. ---------------------------------------------------------------------- dseligo · 2022-07-27 22:28 Quote from: Thausand on July 27, 2022, 02:15:34 amQuote from: dseligo on July 27, 2022, 02:01:12 amQuote from: furious programming on July 26, 2022, 03:47:14 pmCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i PixelA += 1; += Ehhhhhh documentation read:  https://www.freepascal.org/docs- html/prog/progsu10.html I know it can be used. But I made a comment because I don't like it. ---------------------------------------------------------------------- circular · 2022-07-30 06:23 Actually in BGRABitmap you have BGRADiff (or BGRAWordDiff to be more precise) function that you can apply to two pixels and give you the a color difference that is close to perceived difference. Based on winni example: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses ....,BGRABitmap, BGRABitmapTypes;..... procedure TForm1.Button1Click(Sender: TObject);var bmp1,bmp2 : TBGRAbitmap;    p1,p2:PBGRAPixel;    i : integer;    diff: int64;begin  bmp1 := TBGRAbitmap.create(image1.Picture.Bitmap);  bmp2 := TBGRAbitmap.create(image2.Picture.Bitmap);  if bmp1.NbPixels <> bmp2.NbPixels then  begin    showMessage ('Different Dimensions!');  exit;  end;  p1 := bmp1.data;  p2 := bmp2.data;  for i := 0 to bmp1.NbPixels-1 do  begin    inc(diff, BGRADiff(p1^, p2^));    inc(p1);    inc(p2)  end;  // average difference of pixels  diff := round(diff / bmp1.NbPixels);  bmp1.free;  bmp2.free;end; Maybe doing the sum of squares, divide by pixel count and then square root would be better statistically. ---------------------------------------------------------------------- Thaddy · 2022-07-30 09:01 The simplest way is doing a hash on both. Does not need a secure hash. But, given the outlay of your architecture I suspect you maybe want to detect if an image contains stenography compared to the original: in that case simply use a hash, elf-hash may suffice and is extremely fast (7 ops in assembler, 15 ops in pure pascal on Intel). And if the hashes do not match you can further investigate pixel by pixel, which is of course much slower. But that is a real life scenario. ---------------------------------------------------------------------- Pi · 2022-07-31 11:56 Quote from: circular on July 30, 2022, 08:23:15 amActually in BGRABitmap you have BGRADiff (or BGRAWordDiff to be more precise) function that you can apply to two pixels and give you the a color difference that is close to perceived difference... Thanks. I'll try that way too. Quote from: Thaddy on July 30, 2022, 11:01:14 amThe simplest way is doing a hash on both. Does not need a secure hash. But, given the outlay of your architecture I suspect you maybe want to detect if an image contains stenography compared to the original: in that case simply use a hash, elf-hash may suffice and is extremely fast (7 ops in assembler, 15 ops in pure pascal on Intel). And if the hashes do not match you can further investigate pixel by pixel, which is of course much slower. Thanks. The original reason for this in the original post was to save having to rotoscope 642 frames of video which would take a long time (because the source video which was 814 frames had already been rotoscoped and that 814 frame video seemed to have just been edited down with various cuts and re-timings (eg. speed ups) to get to the 642 frame video, so it should have just been a case of copying the equivalent rotoscoped frames from the 814 frame video to create the  642 frame video. But they probably wouldn't have matched exactly due to re-compression of the video. So if a hash looked for the exact match of the pixel values to see if it was the same frame it probably wouldn't work correctly in that case due to re-compression of the video making the rgb values in the exported frames slightly different. I did think another way could be to look at either smaller resolution bmps versions of each frame first or only looking at every so many pixels of the bmps (in the original post I'd mentioned only checking every 100 pixels of the x & y but that it was still not working fast enough that way). eg. in theory you could look at a quarter size bitmaps first (or some other low enough resolution) to find the ones that look closest rgb-wise at that size, then just looking at the closest so many ones at half size, then the closest ones at full size. So that may be a faster way to find the closest frame rgb-wise than comparing just every full size pixel of every image (since at full res you'd only be checking the ones that were close at the lower resolutions). There's probably quite a lot of uses for things like this though. Just finding duplicated frames in one video should also help with rotoscoping work since it should allow you to calculate if a 60 fps video really contains just a 24 or 30 source video or similar low fps video within a video encoded at higher fps (in which case you could just export it at the true frame rate and roto that, and save a lot of time rotoscoping. The original thing about copying rotoscoped matching frames (.bmps) might also work for much longer videos (especially if just the lower res versions are compared first, to speed it up), eg. if you needed to rotoscope a 30-60 minute talking head video, it might be that just a 5 minute section would be enough to get close enough mask shapes for the rest, and you could use these "closest frames" functions to copy the rotoscoped 5 minute vid frames to the closest frames of the 30-60 minute video, creating an accurate enough rotoscoped video up to 60 mins. Though maybe some AI might be a better way for that.