AutoHotkey webinar: Optimizing Image Comparisons & Detection

In this webinar Jackie demonstrated the script he developed for optimizing image detection.

Video Hour 1High-level overview
Video Hour 2Q&A

The script we highlighted was WinSpy – Windows Information Tool from Alguimist.

Here is the script Jackie demonstrated during the webinar  Be sure you download the GDIP.ahk file and have it in your library.
If !pToken := Gdip_Startup()
{
MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system
ExitApp
}

FileSelectFile, File1, , , Select File 1
FileSelectFolder, folder1,

;~ FileSelectFile, File2, , , Select File 2

SplitPath, File1,, outDir

Gui, 1:+AlwaysOnTop +ToolWindow +LastFound

pBitmap1 := Gdip_CreateBitmapFromFile(File1)
;~ pResizedBitmap1 := Gdip_ResizeBitmap(pBitmap1, 16, 16)
;~ pResizedGrayBitmap1 := GetGrayScaleVersion(pResizedBitmap1)
;~ msgbox % (grayScaleValues1 := GetColorValues(pResizedGrayBitmap1))[4,4] ; get color at 4×4

Loop Files, %folder1%\*.jpg
{
pBitmap2 := Gdip_CreateBitmapFromFile(A_LoopFileFullPath)
PercentageDifference := PercentageDifference(pBitmap1, pBitmap2)
BhattacharyyaDifference := BhattacharyyaDifference(GetColorValues(Gdip_ResizeBitmap(GetGrayScaleVersion(pBitmap1))), GetColorValues(Gdip_ResizeBitmap(GetGrayScaleVersion(pBitmap2))))
if ((PercentageDifference * 100 ) < 10)
msgbox % A_LoopFileFullPath
}

;~ pBitmap2 := Gdip_CreateBitmapFromFile(File2)
;~ pResizedBitmap2 := Gdip_ResizeBitmap(pBitmap2, 16, 16)
;~ pResizedGrayBitmap2 := GetGrayScaleVersion(pResizedBitmap2)
;~ msgbox % (grayScaleValues2 := GetColorValues(pResizedGrayBitmap2))[4,4] ; get color at 4×4

;~ Gui, 1: Add, Pic, x0 y0, %File1%
;~ Gui, 1: Add, Pic, y0, %File2%
;~ Gui, 1: Add, Button, gCompare, compare
;~ Gui, 1: Show,, Differences
;~ return

;~ Compare:
;~ Gui, 1: submit
;~ PercentageDifference := PercentageDifference(pBitmap1, pBitmap2)
;~ BhattacharyyaDifference := BhattacharyyaDifference(GetColorValues(Gdip_ResizeBitmap(GetGrayScaleVersion(pBitmap1))), GetColorValues(Gdip_ResizeBitmap(GetGrayScaleVersion(pBitmap2))))
;~ msgbox % “PercentageDifference: ” PercentageDifference “`nBhattacharyyaDifference: ” BhattacharyyaDifference

;~ Gdip_SaveBitmapToFile(Gdip_ResizeBitmap(GetGrayScaleVersion(pBitmap1)), outDir “\resizedimage.jpg”, 100)

;~ return

;~ return

3GuiClose:
2GuiClose:
GuiClose:
Gdip_Shutdown(pToken)
ExitApp

#Include <Gdip.ahk> ;Be sure to download this file and put it in your library

; =========================================================================================================

; returns = how different two images are as a percentage value based on a threshold
PercentageDifference(pBitmap1, pBitmap2, threshold = 3)
{
pResizedBitmap1 := Gdip_ResizeBitmap(pBitmap1, 16, 16)
pResizedGrayBitmap1 := GetGrayScaleVersion(pResizedBitmap1)

pResizedBitmap2 := Gdip_ResizeBitmap(pBitmap2, 16, 16)
pResizedGrayBitmap2 := GetGrayScaleVersion(pResizedBitmap2)

differences := GetDifferences(GetColorValues(pResizedGrayBitmap1), GetColorValues(pResizedGrayBitmap2))
num = 0
numArray := differences

Loop % numArray.length()
{
index1 := A_Index
Loop % numArray.length()
{
index2 := A_Index
;~ msgbox % numArray[index2, index1]
if (numArray[index2, index1] > threshold)
num++
}
}
return (num / 256)
}

; returns = array of integer Differences of every pixel in the two images’
GetDifferences(firstArray, secondArray)
{
numArray := {}
Loop % firstArray.length()
{
index1 := A_Index
Loop % firstArray.length()
{
index2 := A_Index
numArray[index2, index1] := Abs(firstArray[index2, index1] – secondArray[index2, index1])
}
}
return numArray
}

; returns = the amount of red color of each pixel in an array
GetColorValues(pBitmap)
{
Gdip_GetImageDimensions(pBitmap, origW, origH)
numArray := {}
Loop % origW
{
x := A_Index
Loop % origH
{
y := A_Index
;~ msgbox % Abs(GetColor(pBitmap, x, y).R)
numArray[x, y] := Abs(GetColor(pBitmap, x, y).R)
}
}
return numArray
}

; get a pixel color from coords
GetColor(pBitmap, xpos, ypos)
{
ret := GDIP_GetPixel(pBitmap, xpos, ypos)
ret := ARGBtoRGB(ret)
return new CColor(ret)
}

; Converts RGB with Alpha Channel to RGB
ARGBtoRGB( ARGB ){
SetFormat, IntegerFast, hex
ARGB := ARGB & 0x00ffffff
ARGB .= “” ; Necessary due to the “fast” mode.
SetFormat, IntegerFast, d
return ARGB
}

; color class – provides r/g/b values via Dynamic Properties
Class CColor {
__New(RGB){
this._RGB := RGB
}

; Implement RGB and R, G, B as Dynamic Properties
__Get(aName := “”){
if (aName = “RGB”){
; Return RGB in Hexadecimal (eg 0xFF00AA) format
SetFormat, IntegerFast, hex
ret := this._RGB
ret += 0
ret .= “”
SetFormat, IntegerFast, d
return ret
} else if (aName = “R”){
; Return red in Decimal format
return (this._RGB >> 16) & 255
} else if (aName = “G”){
return (this._RGB >> 8) & 255
} else if (aName = “B”){
return this._RGB & 255
}
}

; Compares this pixel to a provided color, with a tolerance
Compare(c2, tol := 20){
return PixelCompare(this, c2, tol)
}
}

; Compares two r/g/b integer objects, with a tolerance
; returns true or false
; Note! simply compares two rgb Hex colors.
PixelCompare(c1, c2, tol := 20) {
return (PixelDiff(c1,c2) <= tol)
}

; Returns the Difference between two r/g/b integer colors objects
PixelDiff(c1,c2){
diff := Abs( c1.r – c2.r ) “,” Abs( c1.g – c2.g ) “,” Abs( c1.b – c2.b )
sort diff,N D,

StringSplit, diff, diff, `,
return diff%diff0%
}

; Returns = the gray scale version of an image using a color Matrix
GetGrayScaleVersion(pBitmap)
{
Gdip_GetImageDimensions(pBitmap, origW, origH)
pBitmap2 := Gdip_CreateBitmap(origW, origH)
pGraphics := Gdip_GraphicsFromImage(pBitmap2)
MatrixGreyScale = 0.299|0.299|0.299|0|0|0.587|0.587|0.587|0|0|0.114|0.114|0.114|0|0|0|0|0|1|0|0|0|0|0|1
Gdip_DrawImage(pGraphics, pBitmap, 0, 0, origW, origH, 0, 0, origW, origH, MatrixGreyScale)
return pBitmap2
}

; Bhattacharyya histogram algorithm, comparing two images based on their normalized histograms
; This tells something about the differences in the brightness of the images as a whole, not so much about where they differ.
; param1 = The first image to compare
; param2 = The second image to compare
; returns = The difference between the images’ normalized histograms
BhattacharyyaDifference(img1GrayscaleValuesArray, img2GrayscaleValuesArray)
{
numArray1 := {}
numArray2 := {}
num1 := 0.0
num2 := 0.0
numArray3 := img1GrayscaleValuesArray
Loop % numArray3.length()
{
index1 := A_Index
Loop % numArray3.length()
{
index2 := A_Index
num3 := numArray3[index1, index2]
num1 += num3
}
}
numArray4 := img2GrayscaleValuesArray
Loop % numArray4.length()
{
index1 := A_Index
Loop % numArray4.length()
{
index2 := A_Index
num3 := numArray4[index1, index2]
num2 += num3
}
}
Loop % img1GrayscaleValuesArray.length()
{
index1 := A_Index
Loop % img1GrayscaleValuesArray.length()
{
index2 := A_Index
numArray1[index1, index2] := img1GrayscaleValuesArray[index1, index2] / num1
}
}
Loop % img2GrayscaleValuesArray.length()
{
index1 := A_Index
Loop % img2GrayscaleValuesArray.length()
{
index2 := A_Index
numArray2[index1, index2] := img2GrayscaleValuesArray[index1, index2] / num2
}
}
num4 := 0
Loop % img2GrayscaleValuesArray.length()
{
index1 := A_Index
Loop % img2GrayscaleValuesArray.length()
{
index2 := A_Index
d := numArray1[index1, index2] * numArray2[index1, index2]
num4 += Sqrt(d)
}
}
return Round(Sqrt(Round(1.0 – num4, 8)), 8)
}

; Examples:
; pResizedBitmap := Gdip_ResizeBitmap(pBitmap, 50, 50) ; resizes to 50x50pixels
Gdip_ResizeBitmap(pBitmap, newWidth=16, newHeight=16) { ; returns resized bitmap. By Learning one.

pBitmap2 := Gdip_CreateBitmap(newWidth, newHeight)
graphics := Gdip_GraphicsFromImage(pBitmap2)
Gdip_SetSmoothingMode(graphics, 2)
Gdip_SetInterpolationMode(graphics, 7)
Gdip_SetPixelOffsetMode(graphics, 2)
Gdip_DrawImage(graphics, pBitmap, 0, 0, NewWidth, NewHeight)
Gdip_DeleteGraphics(graphics)

return pBitmap2
} ; http://www.autohotkey.com/community/viewtopic.php?p=477333#p477333

; Default = 0
; HighSpeed = 1
; HighQuality = 2
; ModeNone = 3
; ModeHalf = 4
Gdip_SetPixelOffsetMode(pGraphics, PixelOffsetMode)
{
return DllCall(“gdiplus\GdipSetPixelOffsetMode”, A_PtrSize ? “UPtr” : “UInt”, pGraphics, “int”, PixelOffsetMode)
}
 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.