Что нового

Попиксельное сравнение изображений. Ускорить обработку

idbehold

Новичок
Сообщения
42
Репутация
4
Добрый день.
Есть два изображения, одинакового разрешения.
Нужно их сравнить и получить разницу, с учетом допустимого отклонения цвета
Для расчета отклонения я использую формулу расстояния между точками.
Для получения информации о цвете пикселя использую GdipBitmapGetPixel

Все работает, я получаю нужный результат, но скрипт работает медленно.
На сравнение изображений размером 500х300 у меня уходит 5 секунд.

Как можно ускорить обработку? Именно обход по всем пикселям изображения - мне не нужно ничего рисовать на GUI и тд - просто сравнить и найти разницу.
Есть какой-то способ быстро обойти все пиксели на изображении?

Вот скрипт:

Код:
#cs ----------------------------------------------------------------------------
 AutoIt Version: 3.3.14.5
#ce ----------------------------------------------------------------------------

#include <GDIPlus.au3>
#include <GUIConstants.au3>

_GDIPlus_Startup()

$hImageOne = _GDIPlus_ImageLoadFromFile(@ScriptDir & '\test_image_1.png')
$hImageTwo = _GDIPlus_ImageLoadFromFile(@ScriptDir & '\test_image_2.png')

Global $iImageWidth  = _GDIPlus_ImageGetWidth($hImageOne)
Global $iImageHeight = _GDIPlus_ImageGetHeight($hImageOne)

If ($iImageWidth <>  _GDIPlus_ImageGetWidth($hImageTwo) or $iImageHeight <>  _GDIPlus_ImageGetHeight($hImageTwo)) Then 
	ConsoleWriteError('Images must have equal size' &  @CRLF)
	Exit;
EndIf


Global $g_hGUI = GUICreate("Debug GUI", $iImageWidth, $iImageHeight)
Global $g_hGraphic = _GDIPlus_GraphicsCreateFromHWND($g_hGUI)
GUISetState(@SW_SHOW)

_GDIPlus_GraphicsDrawImage($g_hGraphic, $hImageOne, 0, 0)

ImageDiff_GetPixel($hImageOne, $hImageTwo, 0.3)

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop
    EndSwitch
WEnd

_GDIPlus_ImageDispose($hImageOne)
_GDIPlus_ImageDispose($hImageTwo)
_GDIPlus_Shutdown()


;----------------------------------------------------------
; ImageDiff
;----------------------------------------------------------
Func ImageDiff_GetPixel($hImgOne, $hImgTwo, $tolerance)
	
	$dx =  441 * $tolerance
	
	Local $countEqualsPixels =  0
	Local $timer =  TimerInit()
	
	Local $tArgb = DllStructCreate("dword Argb")
	Local $pArgb = DllStructGetPtr($tArgb)
	Local $colorBinOne, $colorBinTwo 
	
	For $iY = 0 to $iImageHeight - 1
		
		_GDIPlus_GraphicsDrawImage($g_hGraphic, $hImageOne, 0, 0)
		
		For $iX = 0 to $iImageWidth - 1
			
			; Get pixel RGB at x.y
			DllCall($__g_hGDIPDll, "int", "GdipBitmapGetPixel", "hwnd", $hImgOne, "int", $iX, "int", $iY, "ptr", $pArgb)
			Local $colorOne = '0x' & Hex(DllStructGetData($tArgb, "Argb"), 8)
			$iOneRed 	        =  BitAND(BitShift($colorOne, 16), 0xFF)
			$iOneGreen 	        =  BitAND(BitShift($colorOne, 8), 0xFF)
			$iOneBlue 	        =  BitAND($colorOne, 0xFF)
			
			DllCall($__g_hGDIPDll, "int", "GdipBitmapGetPixel", "hwnd", $hImgTwo, "int", $iX, "int", $iY, "ptr", $pArgb)
			Local $colorTwo = '0x' & Hex(DllStructGetData($tArgb, "Argb"), 8)
			$iTwoRed 	        =  BitAND(BitShift($colorTwo, 16), 0xFF)
			$iTwoGreen 	        =  BitAND(BitShift($colorTwo, 8), 0xFF)
			$iTwoBlue 	        =  BitAND($colorTwo, 0xFF)
			
			$distance =  Sqrt(  ($iOneRed - $iTwoRed)^2 + ($iOneGreen - $iTwoGreen)^2 + ($iOneBlue - $iTwoBlue)^2 )
			
			if $distance < $dx Then 
				$countEqualsPixels += 1
			Else 
				; not equal logic
				DllCall($__g_hGDIPDll, "int", "GdipBitmapSetPixel", "hwnd", $hImgOne, "int", $iX, "int", $iY, "dword", 0xFF000000)
			EndIf
			
		Next
	Next
	
	$countPixels =  $iImageWidth * $iImageHeight
	ConsoleWrite('Diff time		: ' & TimerDiff($timer) &  @CRLF);	
	ConsoleWrite('percent 		: ' &  (($countEqualsPixels / $countPixels ) * 100) &  @CRLF);	
	ConsoleWrite('pixels		: ' & $countEqualsPixels & '/' & $countPixels &  @CRLF);	
	
EndFunc



Во вложении архив с этим скриптом и исходные картинки
 

Вложения

  • image_diff.zip
    99.1 КБ · Просмотры: 7

Prog

Продвинутый
Сообщения
583
Репутация
71
Дело в медленной работе скрипта autoit и в выводе результата на экран в реальном времени.
По быстрому переписал на PB для сравнения. Ускорение составило почти 300 раз. Задача нормально параллелится и если картинку разделить на число ядер процессора и сравнивать в нескольких потоках, прирост производительности будет выше на число ядер процессора.
Код:
DisableDebugger
EnableExplicit
Define ImageOne, ImageTwo

UsePNGImageDecoder()


Structure RGB
  r.a
  g.a
  b.a
EndStructure

Structure RGB_Color
  StructureUnion
    RGB.RGB
    Color.l
  EndStructureUnion
EndStructure

Procedure ImageToArray(Img, Array Pic.RGB_Color(2))
  Protected x, y, w, h
  
  StartDrawing(ImageOutput(Img))
  w = OutputWidth()-1
  h = OutputHeight()-1
  
  For x=0 To w
    For y=0 To h
      Pic(x, y)\Color = Point(x, y)
    Next
  Next
  
  StopDrawing()
EndProcedure

Procedure ImageDiff_GetPixel(ImgOne, ImgTwo, tolerance.d)
  Protected dx.d = 441 * tolerance, countEqualsPixels = 0
  Protected timer = ElapsedMilliseconds(), countPixels
  Protected colorBinOne, colorBinTwo, distance.d
  Protected x, y, w=ImageWidth(ImgOne), h=ImageHeight(ImgOne)
  Protected Dim Data1.RGB_Color(w, h)
  Protected Dim Data2.RGB_Color(w, h)
  
  ImageToArray(ImgOne, Data1())
  ImageToArray(ImgTwo, Data2())
  
  For x=0 To w-1
    For y=0 To h-1
      
      distance =  Sqr(Pow(Data1(x, y)\RGB\r - Data2(x, y)\RGB\r, 2) + 
                      Pow(Data1(x, y)\RGB\g - Data2(x, y)\RGB\g, 2) + 
                      Pow(Data1(x, y)\RGB\b - Data2(x, y)\RGB\b, 2))
      
      If distance < dx
        countEqualsPixels + 1
      EndIf
      
    Next
  Next
  
  countPixels = w * h
  MessageRequester("", "Diff time : "+Str(ElapsedMilliseconds()-timer)+#CRLF$+
                       "percent   : "+StrF((countEqualsPixels / countPixels ) * 100, 3)+#CRLF$+
                       "pixels    : "+countEqualsPixels + "/" + countPixels)
EndProcedure


ImageOne = LoadImage(#PB_Any, "test_image_1.png")
ImageTwo = LoadImage(#PB_Any, "test_image_2.png")

If ImageOne=0 Or ImageTwo=0
  MessageRequester("", "Картинки не загружены")
  End
EndIf

If ImageWidth(ImageOne) <> ImageWidth(ImageTwo) Or ImageHeight(ImageOne) <> ImageHeight(ImageTwo)
  MessageRequester("", "Images must have equal size")
  End
EndIf

ImageDiff_GetPixel(ImageOne, ImageTwo, 0.3)

FreeImage(ImageOne)
FreeImage(ImageTwo)
 

Вложения

  • image_diff_pb.7z
    199.9 КБ · Просмотры: 3
Автор
idbehold

idbehold

Новичок
Сообщения
42
Репутация
4
Prog, спасибо за ответ. Да, на Purebasic выходит гораздо быстрее.
Я, конечно, подумаю над реализацией на другом языке, но не хотелось бы отказываться от автоита

Пока что пришел к такому варианту, который чуть быстрее

Код:
;----------------------------------------------------------
; ImageDiff
;----------------------------------------------------------
Func ImageDiff_BitMapBits($hImgOne, $hImgTwo, $tolerance)
	
	$dx =  441 * $tolerance
	
	Local $countEqualsPixels	=  0
	Local $timer 				=  TimerInit()
	
	$hBitMapOne = _GDIPlus_BitmapCreateHBITMAPFromBitmap($hImageOne)
	$hBitMapTwo = _GDIPlus_BitmapCreateHBITMAPFromBitmap($hImageTwo)

	$size =  $iImageWidth *  $iImageHeight
	$tBitsOne = DllStructCreate('dword[' & $size & ']')
	$tBitsTwo = DllStructCreate('dword[' & $size & ']')
	
	_WinAPI_GetBitmapBits($hBitMapOne, 4 * $size, DllStructGetPtr($tBitsOne))
	_WinAPI_GetBitmapBits($hBitMapTwo, 4 * $size, DllStructGetPtr($tBitsTwo))

	
	
	For $iY = 0 to $iImageHeight - 1
		
;~ 		_GDIPlus_GraphicsDrawImage($g_hGraphic, $hImageOne, 0, 0)
		
		For $iX = 0 to $iImageWidth - 1
			$Offset =  $iY * $iImageWidth +  $iX + 1
			
			$colorOne = '0x' & Hex(DllStructGetData($tBitsOne, 1, $Offset),  8)
			$iOneRed 	=  BitAND(BitShift($colorOne, 16), 0xFF)
			$iOneGreen	=  BitAND(BitShift($colorOne, 8), 0xFF)
			$iOneBlue	=  BitAND($colorOne, 0xFF)
			
			$colorTwo = '0x' & Hex(DllStructGetData($tBitsTwo, 1, $Offset),  8)
			$iTwoRed 	=  BitAND(BitShift($colorTwo, 16), 0xFF)
			$iTwoGreen	=  BitAND(BitShift($colorTwo, 8), 0xFF)
			$iTwoBlue 	=  BitAND($colorTwo, 0xFF)
			
			$distance =  Sqrt(  ($iOneRed - $iTwoRed)^2 + ($iOneGreen - $iTwoGreen)^2 + ($iOneBlue - $iTwoBlue)^2 )
			
			if $distance < $dx Then 
				$countEqualsPixels += 1
			Else 
				; not equal logic
;~ 				DllCall($__g_hGDIPDll, "int", "GdipBitmapSetPixel", "hwnd", $hImgOne, "int", $iX, "int", $iY, "dword", 0xFF000000)
			EndIf
		Next
	Next


Но даже просто пустые циклы (без вычислений) с вызовом DllStructGetData внутри работают крайне медленно.
Даже не знаю в какую сторону дальше копать. Может написать то же самое на VBS и вызывать через ScriptControl?
 

Prog

Продвинутый
Сообщения
583
Репутация
71
idbehold [?]
Может написать то же самое на VBS и вызывать через ScriptControl?
Проще код что выложил выше, собрать как dll и вызывать из AutoIt или выводить результат в консоль и читать из нее.
VBS такой же интерпретируемый как AutoIt. Если получится ускорить, то не на много.
 
Автор
idbehold

idbehold

Новичок
Сообщения
42
Репутация
4
Prog сказал(а):
Проще код что выложил выше, собрать как dll и вызывать из AutoIt

Да использовать dll вполне себе вариант, правда все эти сравнения картинок только часть задачи, раз уж пошла такая пьянка, то имеет смысл все на другом языке писать.
Но покупать лицензию PB у меня точно никакого желания нет =)

Ладно, думаю тему можно закрыть.
 

Prog

Продвинутый
Сообщения
583
Репутация
71
idbehold [?]
Но покупать лицензию PB у меня точно никакого желания нет
Код компилируется в бесплатной версии. Через консоль нормально работает.
 

Вложения

  • image_diff_pb.7z
    200 КБ · Просмотры: 7
Верх