Percentage difference between images: Difference between revisions
(→{{header|Python}}: where is Image package ?) |
(autohotkey example) |
||
Line 56: | Line 56: | ||
Close (F1); |
Close (F1); |
||
Close (F2);</lang> |
Close (F2);</lang> |
||
=={{header|AutoHotkey}}== |
|||
{{works with | AutoHotkey_L}} |
|||
uses [http://www.autohotkey.com/forum/topic32238.html gdip.ahk] |
|||
<lang AutoHotkey>startup() |
|||
dibSection := getPixels("lenna100.jpg") |
|||
dibSection2 := getPixels("lenna50.jpg") ; ("File-Lenna100.jpg") |
|||
pixels := dibSection.pBits |
|||
pixels2 := dibSection2.pBits |
|||
z := 0 |
|||
loop % dibSection.width * dibSection.height * 4 |
|||
{ |
|||
x := numget(pixels - 1, A_Index, "uchar") |
|||
y := numget(pixels2 - 1, A_Index, "uchar") |
|||
z += abs(y - x) |
|||
} |
|||
msgbox % z / (dibSection.width * dibSection.height * 3 * 255 / 100 ) ; 1.626 |
|||
return |
|||
CreateDIBSection2(hDC, nW, nH, bpp = 32, ByRef pBits = "") |
|||
{ |
|||
dib := object() |
|||
NumPut(VarSetCapacity(bi, 40, 0), bi) |
|||
NumPut(nW, bi, 4) |
|||
NumPut(nH, bi, 8) |
|||
NumPut(bpp, NumPut(1, bi, 12, "UShort"), 0, "Ushort") |
|||
NumPut(0, bi,16) |
|||
hbm := DllCall("gdi32\CreateDIBSection", "Uint", hDC, "Uint", &bi, "Uint", 0, "UintP", pBits, "Uint", 0, "Uint", 0) |
|||
dib.hbm := hbm |
|||
dib.pBits := pBits |
|||
dib.width := nW |
|||
dib.height := nH |
|||
dib.bpp := bpp |
|||
dib.header := header |
|||
Return dib |
|||
} |
|||
startup() |
|||
{ |
|||
global disposables |
|||
disposables := object() |
|||
disposables.pBitmaps := object() |
|||
disposables.hBitmaps := object() |
|||
If !(disposables.pToken := Gdip_Startup()) |
|||
{ |
|||
MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system |
|||
ExitApp |
|||
} |
|||
OnExit, gdipExit |
|||
} |
|||
gdipExit: |
|||
loop % disposables.hBitmaps._maxindex() |
|||
DllCall("DeleteObject", "Uint", disposables.hBitmaps[A_Index]) |
|||
Gdip_Shutdown(disposables.pToken) |
|||
ExitApp |
|||
getPixels(imageFile) |
|||
{ |
|||
global disposables ; hBitmap will be disposed later |
|||
pBitmapFile1 := Gdip_CreateBitmapFromFile(imageFile) |
|||
hbmi := Gdip_CreateHBITMAPFromBitmap(pBitmapFile1) |
|||
width := Gdip_GetImageWidth(pBitmapFile1) |
|||
height := Gdip_GetImageHeight(pBitmapFile1) |
|||
mDCo := DllCall("CreateCompatibleDC", "Uint", 0) |
|||
mDCi := DllCall("CreateCompatibleDC", "Uint", 0) |
|||
dibSection := CreateDIBSection2(mDCo, width, height) |
|||
hBMo := dibSection.hbm |
|||
oBM := DllCall("SelectObject", "Uint", mDCo, "Uint", hBMo) |
|||
iBM := DllCall("SelectObject", "Uint", mDCi, "Uint", hbmi) |
|||
DllCall("BitBlt", "Uint", mDCo, "int", 0, "int", 0, "int", width, "int", height, "Uint", mDCi, "int", 0, "int", 0, "Uint", 0x40000000 | 0x00CC0020) |
|||
DllCall("SelectObject", "Uint", mDCo, "Uint", oBM) |
|||
DllCall("ReleaseDC", "Uint", 0, "Uint", mDCi) |
|||
DllCall("ReleaseDC", "Uint", 0, "Uint", mDCo) |
|||
Gdip_DisposeImage(pBitmapFile1) |
|||
DllCall("DeleteObject", "Uint", hBMi) |
|||
disposables.hBitmaps._insert(hBMo) |
|||
return dibSection |
|||
} |
|||
#Include Gdip.ahk ; Thanks to tic (Tariq Porter) for his GDI+ Library |
|||
</lang> |
|||
=={{header|C}}== |
=={{header|C}}== |
||
Revision as of 02:51, 7 June 2010
You are encouraged to solve this task according to the task description, using any language you may know.
Compute the percentage of difference between 2 JPEG images of the same size. Alternatively, compare two bitmaps as defined in basic bitmap storage.
Useful for comparing two JPEG images saved with a different compression ratios.
You can use these pictures for testing (use the full-size version of each):
50% quality JPEG | 100% quality JPEG |
---|---|
The expected difference for these two images is 1.62125%
Ada
<lang ada>type Count is mod 2**64;</lang> 1-norm distance in the luminance space: <lang ada>function "-" (Left, Right : Luminance) return Count is begin
if Left > Right then return Count (Left) - Count (Right); else return Count (Right) - Count (Left); end if;
end "-";</lang> 1-norm distance in the color space multiplied to 3: <lang ada>function "-" (Left, Right : Pixel) return Count is begin
return (Left.R - Right.R) + (Left.G - Left.G) + (Left.B - Right.B);
end "-";</lang> Mean of 1-norm distances. Constraint_Error is propagated when images have different size. <lang ada>function Diff (Left, Right : Image) return Float is
Offs_I : constant Integer := Right'First (1) - Left'First (1); Offs_J : constant Integer := Right'First (2) - Left'First (2); Sum : Count := 0;
begin
if Left'Length (1) /= Right'Length (1) or else Left'Length (2) /= Right'Length (2) then raise Constraint_Error; end if; for I in Left'Range (1) loop for J in Left'Range (2) loop Sum := Sum + (Left (I, J) - Right (I + Offs_I, J + Offs_J)); end loop; end loop; return Float (Sum) / (3.0 * Float (Left'Length (1) * Left'Length (2)));
end Diff;</lang> Example of use: <lang ada> F1, F2 : File_Type; begin
Open (F1, In_File, "city.ppm"); Open (F2, In_File, "city_emboss.ppm"); Ada.Text_IO.Put_Line ("Diff" & Float'Image (Diff (Get_PPM (F1), Get_PPM (F2)))); Close (F1); Close (F2);</lang>
AutoHotkey
uses gdip.ahk <lang AutoHotkey>startup() dibSection := getPixels("lenna100.jpg") dibSection2 := getPixels("lenna50.jpg") ; ("File-Lenna100.jpg") pixels := dibSection.pBits pixels2 := dibSection2.pBits z := 0 loop % dibSection.width * dibSection.height * 4 { x := numget(pixels - 1, A_Index, "uchar") y := numget(pixels2 - 1, A_Index, "uchar") z += abs(y - x) } msgbox % z / (dibSection.width * dibSection.height * 3 * 255 / 100 ) ; 1.626 return
CreateDIBSection2(hDC, nW, nH, bpp = 32, ByRef pBits = "") { dib := object() NumPut(VarSetCapacity(bi, 40, 0), bi) NumPut(nW, bi, 4) NumPut(nH, bi, 8) NumPut(bpp, NumPut(1, bi, 12, "UShort"), 0, "Ushort") NumPut(0, bi,16) hbm := DllCall("gdi32\CreateDIBSection", "Uint", hDC, "Uint", &bi, "Uint", 0, "UintP", pBits, "Uint", 0, "Uint", 0)
dib.hbm := hbm dib.pBits := pBits dib.width := nW dib.height := nH dib.bpp := bpp dib.header := header Return dib }
startup() { global disposables disposables := object() disposables.pBitmaps := object() disposables.hBitmaps := object()
If !(disposables.pToken := Gdip_Startup()) { MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system ExitApp }
OnExit, gdipExit
}
gdipExit:
loop % disposables.hBitmaps._maxindex()
DllCall("DeleteObject", "Uint", disposables.hBitmaps[A_Index])
Gdip_Shutdown(disposables.pToken)
ExitApp
getPixels(imageFile) { global disposables ; hBitmap will be disposed later pBitmapFile1 := Gdip_CreateBitmapFromFile(imageFile) hbmi := Gdip_CreateHBITMAPFromBitmap(pBitmapFile1) width := Gdip_GetImageWidth(pBitmapFile1) height := Gdip_GetImageHeight(pBitmapFile1)
mDCo := DllCall("CreateCompatibleDC", "Uint", 0) mDCi := DllCall("CreateCompatibleDC", "Uint", 0) dibSection := CreateDIBSection2(mDCo, width, height) hBMo := dibSection.hbm
oBM := DllCall("SelectObject", "Uint", mDCo, "Uint", hBMo) iBM := DllCall("SelectObject", "Uint", mDCi, "Uint", hbmi)
DllCall("BitBlt", "Uint", mDCo, "int", 0, "int", 0, "int", width, "int", height, "Uint", mDCi, "int", 0, "int", 0, "Uint", 0x40000000 | 0x00CC0020)
DllCall("SelectObject", "Uint", mDCo, "Uint", oBM) DllCall("ReleaseDC", "Uint", 0, "Uint", mDCi) DllCall("ReleaseDC", "Uint", 0, "Uint", mDCo) Gdip_DisposeImage(pBitmapFile1) DllCall("DeleteObject", "Uint", hBMi) disposables.hBitmaps._insert(hBMo) return dibSection }
- Include Gdip.ahk ; Thanks to tic (Tariq Porter) for his GDI+ Library
</lang>
C
The read_image function is from here.
<lang c>#include <stdio.h>
- include <stdlib.h>
- include <math.h>
/* #include "imglib.h" */
- define RED_C 0
- define GREEN_C 1
- define BLUE_C 2
- define GET_PIXEL(IMG, X, Y) ((IMG)->buf[ (Y) * (IMG)->width + (X) ])
int main(int argc, char **argv) {
image im1, im2; double totalDiff = 0.0; unsigned int x, y; if ( argc < 3 ) { fprintf(stderr, "usage:\n%s FILE1 FILE2\n", argv[0]); exit(1); } im1 = read_image(argv[1]); if ( im1 == NULL ) exit(1); im2 = read_image(argv[2]); if ( im2 == NULL ) { free_img(im1); exit(1); } if ( (im1->width != im2->width) || (im1->height != im2->height) ) { fprintf(stderr, "width/height of the images must match!\n"); } else { /* BODY: the "real" part! */ for(x=0; x < im1->width; x++) { for(y=0; y < im1->width; y++) { totalDiff += fabs( GET_PIXEL(im1, x, y)[RED_C] - GET_PIXEL(im2, x, y)[RED_C] ) / 255.0; totalDiff += fabs( GET_PIXEL(im1, x, y)[GREEN_C] - GET_PIXEL(im2, x, y)[GREEN_C] ) / 255.0; totalDiff += fabs( GET_PIXEL(im1, x, y)[BLUE_C] - GET_PIXEL(im2, x, y)[BLUE_C] ) / 255.0; } } printf("%lf\n", 100.0 * totalDiff / (double)(im1->width * im1->height * 3) ); /* BODY ends */ } free_img(im1); free_img(im2);
}</lang>
The output on Lenna is:
1.625587
Common Lisp
This is based upon the C version. Strangely enough, the percentage is 1.77% which is off by about a tenth of a percent.
<lang lisp>(require 'cl-jpeg)
- the JPEG library uses simple-vectors to store data. this is insane!
(defun compare-images (file1 file2)
(declare (optimize (speed 3) (safety 0) (debug 0))) (multiple-value-bind (image1 height width) (jpeg:decode-image file1) (let ((image2 (jpeg:decode-image file2))) (loop for i of-type (unsigned-byte 8) across (the simple-vector image1) for j of-type (unsigned-byte 8) across (the simple-vector image2) sum (the fixnum (abs (- i j))) into difference of-type fixnum finally (return (coerce (/ difference width height #.(* 3 255)) 'double-float))))))
CL-USER> (* 100 (compare-images "Lenna50.jpg" "Lenna100.jpg")) 1.774856467652165d0</lang>
E
By dividing only at the end, we work with integers only as the sum and avoid floating-point error from adding small numbers (per-pixel difference) to large ones (sum of differences).
<lang e>def imageDifference(a, b) {
require(a.width() == b.width()) require(a.height() == b.height()) def X := 0..!(a.width()) def Y := 0..!(a.height()) var sumByteDiff := 0 for y in Y { for x in X { def ca := a[x, y] def cb := b[x, y] sumByteDiff += (ca.rb() - cb.rb()).abs() \ + (ca.gb() - cb.gb()).abs() \ + (ca.bb() - cb.bb()).abs() } println(y) } return sumByteDiff / (255 * 3 * a.width() * a.height())
}
def imageDifferenceTask() {
println("Read 1...") def a := readPPM(<import:java.io.makeFileInputStream>(<file:Lenna50.ppm>)) println("Read 2...") def b := readPPM(<import:java.io.makeFileInputStream>(<file:Lenna100.ppm>)) println("Compare...") def d := imageDifference(a, b) println(`${d * 100}% different.`)
}</lang>
The result on the provided images is 1.6255930981604882%.
Forth
<lang forth>: pixel-diff ( pixel1 pixel2 -- n )
over 255 and over 255 and - abs >r 8 rshift swap 8 rshift over 255 and over 255 and - abs >r 8 rshift swap 8 rshift - abs r> + r> + ;
- bdiff ( bmp1 bmp2 -- fdiff )
2dup bdim rot bdim d<> abort" images not comparable" 0e ( F: total diff ) dup bdim * >r ( R: total pixels ) bdata swap bdata r@ 0 do over @ over @ pixel-diff 0 d>f f+ cell+ swap cell+ loop 2drop r> 3 * 255 * 0 d>f f/ ;
- .bdiff ( bmp1 bmp2 -- )
cr bdiff 100e f* f. ." percent different" ;</lang>
Fortran
<lang fortran>program ImageDifference
use RCImageBasic use RCImageIO
implicit none
integer, parameter :: input1_u = 20, & input2_u = 21
type(rgbimage) :: lenna1, lenna2 real :: totaldiff
open(input1_u, file="Lenna100.ppm", action="read") open(input2_u, file="Lenna50.ppm", action="read") call read_ppm(input1_u, lenna1) call read_ppm(input2_u, lenna2) close(input1_u) close(input2_u)
totaldiff = sum( (abs(lenna1%red - lenna2%red) + & abs(lenna1%green - lenna2%green) + & abs(lenna1%blue - lenna2%blue)) / 255.0 )
print *, 100.0 * totaldiff / (lenna1%width * lenna1%height * 3.0)
call free_img(lenna1) call free_img(lenna2)
end program ImageDifference</lang>
This gives 1.6555154.
Haskell
This implementation takes PPMs as input. It uses modules defined in Basic bitmap storage and Write ppm file.
<lang haskell>import Bitmap import Bitmap.Netpbm import Bitmap.RGB
import Control.Monad import Control.Monad.ST import System.Environment (getArgs)
main = do
[path1, path2] <- getArgs image1 <- readNetpbm path1 image2 <- readNetpbm path2 diff <- stToIO $ imageDiff image1 image2 putStrLn $ "Difference: " ++ show (100 * diff) ++ "%"
imageDiff :: Image s RGB -> Image s RGB -> ST s Double imageDiff image1 image2 = do
i1 <- getPixels image1 i2 <- getPixels image2 unless (length i1 == length i2) $ fail "imageDiff: Images are of different sizes" return $ toEnum (sum $ zipWith minus i1 i2) / toEnum (3 * 255 * length i1) where (RGB (r1, g1, b1)) `minus` (RGB (r2, g2, b2)) = abs (r1 - r2) + abs (g1 - g2) + abs (b1 - b2)</lang>
MAXScript
<lang maxscript>fn diffImages = ( local img1 = selectBitmap caption:"Select Image 1" local img2 = selectBitmap caption:"Select Image 2" local totalDiff = 0 for i in 0 to (img1.height-1) do ( local img1Row = getPixels img1 [0, i] img1.width local img2Row = getPixels img2 [0, i] img2.width
for j in 1 to img1.width do ( totalDiff += (abs (img1Row[j].r - img2Row[j].r)) / 255.0 totalDiff += (abs (img1Row[j].g - img2Row[j].g)) / 255.0 totalDiff += (abs (img1Row[j].b - img2Row[j].b)) / 255.0 ) ) format "Diff: %\%\n" (totalDiff / ((img1.width * img1.height * 3) as float) * 100) )</lang>
J
<lang j> require 'media/image3'
'Lenna50.jpg' (+/@,@:|@:- % 2.55 * */@$@])&read_image 'Lenna100.jpg'
1.62559</lang>
OCaml
<lang ocaml>#! /usr/bin/env ocaml
- directory "+glMLite/"
- load "jpeg_loader.cma"
- load "bigarray.cma"
open Jpeg_loader
let () =
let img1, width1, height1, col_comp1, color_space1 = load_img (Filename Sys.argv.(1)) and img2, width2, height2, col_comp2, color_space2 = load_img (Filename Sys.argv.(2)) in
assert(width1 = width2); assert(height1 = height2); assert(col_comp1 = col_comp2); (* number of color components *) assert(color_space1 = color_space2);
let img1 = Bigarray.array3_of_genarray img1 and img2 = Bigarray.array3_of_genarray img2 in
let sum = ref 0.0 and num = ref 0 in
for x=0 to pred width1 do for y=0 to pred height1 do for c=0 to pred col_comp1 do let v1 = float img1.{x,y,c} and v2 = float img2.{x,y,c} in let diff = (abs_float (v1 -. v2)) /. 255. in sum := diff +. !sum; incr num; done; done; done;
let diff_percent = !sum /. float !num in Printf.printf " diff: %f percent\n" diff_percent;
- </lang>
Python
<lang python>from itertools import izip import Image
i1 = Image.open("image1.jpg") i2 = Image.open("image2.jpg") assert i1.mode == i2.mode, "Different kinds of images." assert i1.size == i2.size, "Different sizes."
pairs = izip(i1.getdata(), i2.getdata()) if len(i1.getbands()) == 1:
# for gray-scale jpegs dif = sum(abs(p1-p2) for p1,p2 in pairs)
else:
dif = sum(abs(c1-c2) for p1,p2 in pairs for c1,c2 in zip(p1,p2))
ncomponents = i1.size[0] * i1.size[1] * 3 print "Difference (percentage):", (dif / 255.0 * 100) / ncomponents</lang>
REBOL
<lang REBOL>REBOL [ Title: "Percent Image Difference" Author: oofoe Date: 2009-12-31 URL: http://rosettacode.org/wiki/Percentage_of_difference_between_2_images ]
- Load from local storage. Un/comment as preferred.
- a
- load-image %lenna50.jpg
- b
- load-image %lenna100.jpg
- Download from rosettacode.org.
a: load-image http://rosettacode.org/mw/images/3/3c/Lenna50.jpg b: load-image http://rosettacode.org/mw/images/b/b6/Lenna100.jpg
if a/size <> b/size [print "Image dimensions must match." halt]
- Compute difference. REBOL has built-in image processing as part of
- its GUI package that I can take advantage of here
diff: to-image layout/tight [image a effect [difference b]]
- Calculate deviation. I use 'repeat' to rip through the image pixels
- (it knows how to deal with images) and sum, then average. Note that
- I can treat the image like an array to get number of pixels.
t: 0 repeat p diff [t: t + p/1 + p/2 + p/3] print rejoin ["Difference: " 100 * t / (255 * 3 * length? diff) "%"]
- Optional
- Since I now have a difference image, I may as well show
- it. Use the buttons or keys 'a', 'b' and 'd' to switch between the
- various images.
flip: func [ "Change to new image and label." name [word!] "Image to switch to." ][x/text: rejoin ["Image " name] x/image: get name show x]
- Because the differences between the images are very small, I enhance
- the diff with a high contrast to make the result easier to
- see. Comment this out for the "pure" image.
diff: to-image layout/tight [image diff effect [contrast 100]]
view l: layout [ x: image diff across button "a" #"a" [flip 'a] button "b" #"b" [flip 'b] button "difference" #"d" [flip 'diff] ]</lang>
Output:
Difference: 1.62559309816049%
Note that this image has been contrast enhanced to highlight the differences.
Ruby
uses the raster_graphics
library
<lang ruby>require 'raster_graphics'
class RGBColour
# the difference between two colours def -(a_colour) (@red - a_colour.red).abs + (@green - a_colour.green).abs + (@blue - a_colour.blue).abs end
end
class Pixmap
# the difference between two images def -(a_pixmap) if @width != a_pixmap.width or @height != a_pixmap.height raise ArgumentError, "can't compare images with different sizes" end sum = 0 each_pixel {|x,y| sum += self[x,y] - a_pixmap[x,y]} Float(sum) / (@width * @height * 255 * 3) end
end
lenna50 = Pixmap.open_from_jpeg('Lenna50.jpg') lenna100 = Pixmap.open_from_jpeg('Lenna100.jpg')
puts "difference: %.5f%%" % (100.0 * (lenna50 - lenna100))</lang>
produces:
difference: 1.62559%
Tcl
This version uses the Img package, but only to provide a convenient JPEG loader; it's utterly unnecessary for the process of actually computing the difference. <lang tcl>package require Tk
proc imageDifference {img1 img2} {
if {
[image width $img1] != [image width $img2] || [image height $img1] != [image height $img2]
} then {
return -code error "images are different size"
} set diff 0 for {set x 0} {$x<[image width $img1]} {incr x} {
for {set y 0} {$y<[image height $img1]} {incr y} { lassign [$img1 get $x $y] r1 g1 b1 lassign [$img2 get $x $y] r2 g2 b2 incr diff [expr {abs($r1-$r2)+abs($g1-$g2)+abs($b1-$b2)}] }
} expr {$diff/double($x*$y*3*255)}
}
- Package only used for JPEG loader
package require Img image create photo lenna50 -file lenna50.jpg image create photo lenna100 -file lenna100.jpg puts "difference is [expr {[imageDifference lenna50 lenna100]*100.}]%" exit ;# Need explicit exit here; don't want a GUI</lang> It produces this output:
difference is 1.6255930981604882%
Vedit macro language
This implementation compares two BMP images.
<lang vedit>Chdir("|(USER_MACRO)\Rosetta\data") File_Open("Lenna50.bmp", BROWSE)
- 10 = Buf_Num // #10 = buffer for 1st image
File_Open("Lenna100.bmp", BROWSE)
- 20 = Buf_Num // #20 = buffer for 2nd image
Goto_Pos(10) // offset to pixel data Goto_Pos(Cur_Char + Cur_Char(1)*256) Buf_Switch(#10) Goto_Pos(10) Goto_Pos(Cur_Char + Cur_Char(1)*256)
- 15 = 0 // #15 = difference
- 16 = 0 // #16 = total number of samples
While(!At_EOF) {
#11 = Cur_Char; Char Buf_Switch(#20) #21 = Cur_Char; Char #15 += abs(#11-#21) #16++ Buf_Switch(#10)
}
- 19 = #15 / (#16*256/100000)
M("Sum of diff: ") NT(#15) M("Total bytes: ") NT(#16) M("Difference: ") NT(#19/1000,LEFT+NOCR) M(".") NT(#19%1000,LEFT+NOCR) M("%\n")
Buf_Switch(#10) Buf_Quit(OK) Buf_Switch(#20) Buf_Quit(OK)</lang>
Output, when comparing the Lenna images that were converted to BMP:
Sum of diff: 3259967 Total bytes: 786432 Difference: 1.619%