ImageSearch innerhalb JPGs/PNG/TIFF Topic is solved

Stelle Fragen zur Programmierung mit Autohotkey

Moderator: jNizM

Ahk_fan
Posts: 149
Joined: 31 Aug 2018, 14:34
Contact:

ImageSearch innerhalb JPGs/PNG/TIFF

21 Apr 2021, 00:39

Hallo zusammen,

ich habe gesucht, aber nicht gefunden:
Gibt es über AHK eine Möglichkeit (GDI+?) innerhalb einer Bilddatei (JPG/TIFF/PNG) eine Suche nach einem Bildteil zu machen und als Rückgabewert die Koordinaten zu bekommen? Also ImageSearch für Bilddateien statt für den Screen?
Input:
- gescannte TIFF (Datei 1)
- TIFF mit einem Ausschnitt (Datei 2)
Verarbeitung:
- Suche innerhalb eines Bereichs des Inputbildes Datei 1 (Eingabe Beginn X + Y Koordinate, Eingabe Ende X+Y-Koordinate) nach Datei 2 mit Angabe der prozentualen Übereinstimmung, evtl noch Angabe der Auflösung
Output:
- Koordinaten der gefundenen Position

So ungefähr oder ähnlich stelle ich es mir vor, alle Vorschläge sind willkommen und auch Commandlinetools.
Ich habe im Forum gesucht, aber nichts passendes gefunden.

Danke schon mal im Voraus!
regards,
AHK_fan :)
https://hr-anwendungen.de
BoBo
Posts: 4598
Joined: 13 May 2014, 17:15

Re: ImageSearch innerhalb JPGs/PNG/TIFF

21 Apr 2021, 05:14

Kenne mich da null/nada/nix/niente aus, doch könnte mir vorstellen das, sofern Bildelemente als identischer/deckungsgleicher code vorliegen (hätte,wäre,wenn) ein Abgleich machbar wäre? Also, laienhafte Vorstellung - ein Bild in Base64 umgewandelt enthält ein Teilbild wenn der code des Teilbildes im Vollbild vorhanden ist <Platzhalter für 10hoch64 Fragezeichen>. Klingt blöd, isses ziemlich sicher auch, denn, warum baut Google Bilder(ähnlichkkeits)suchalgorithmen wenn das schon die Lösung wäre??

https://towardsdatascience.com/creating-your-own-object-detector-ad69dda69c85 Good luck :thumbup:
Ahk_fan
Posts: 149
Joined: 31 Aug 2018, 14:34
Contact:

Re: ImageSearch innerhalb JPGs/PNG/TIFF

21 Apr 2021, 06:18

danke..ich muss mich da einfach...
es müssen ja unterschiedliche Dinge betrachtet werden
- Skalierung (scale)
- Verdrehung (skew/deskew) --> da habe ich ein Tool, bastel mir aber gerade mit GDIP ein eigenes, dass es mir die Verdrehung vom Bild ermittelt und korrigiert , ganz schön schwierig
- Farbkodierung (sw/grau/farbe)
- prozentuale Übereinstimmung?

ich habe mal eine Auftragsbestätigung und das Suchbild angehängt, im Endeffekt brauche ich die Koordinaten X=1058 / Y=655 und/oder result 1, dass was gefunden wurde.

eine Möglichkeit wäre über OpenCV, aber da hab ich echt keine Ahnung
--> OpenCV
https://www.autohotkey.com/boards/viewtopic.php?t=60442
https://www.autohotkey.com/boards/viewtopic.php?f=76&t=77046
https://github.com/manciuszz/AHK-OpenCV
Attachments
test_suchpix.jpg
test_suchpix.jpg (2.53 KiB) Viewed 194 times
test_AB.jpg
test_AB.jpg (280.95 KiB) Viewed 194 times
regards,
AHK_fan :)
https://hr-anwendungen.de
just me
Posts: 7890
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: ImageSearch innerhalb JPGs/PNG/TIFF

22 Apr 2021, 04:13

Moin,

wegen Deines Beispiels: Wäre OCR eine Alternative? Für Win 10 gibt es da einen sehr gelungenen Ansatz von malcev Optical character recognition (OCR) with UWP API.

Für Deine Auftragsbestätigung liefert das bei einem Vollscan:

Code: Select all

---------------------------
BilddateiOCR.ahk
---------------------------
hagebau
bauen + modernisieren
Auftragsbestätigung
Standardbeleg
Ansprechpartner:
Telefon:
Telefax:
E-Mail:
Kunden-Nr.
21
51
Bauvorhaben
Auftrag-Nr.
12
61
I I I I I II I I I I I I I I II I I I I I III I I I I I I I I I II I I I I I I I I I I II II
Belegdatum
19.11.2019
Kommission Abholung
Vielen Dank für Ihren Auftrag, den wir wie folgt bestätigen.
Wir liefern gemäß unseren Lieferungs- und Zahlungsbedingungen.
Pos
Menge
Seite
Einzelpreis €
13,75
20,90
4,33
1 von 1
PE Gesamtpreis €
Artikel
20002156
20002156
20200241
Nettowert
400,63
Bezeichnung
Remmers Epoxy BS 2000 10kg/GEB
Grundierung basaltgrau RAL7012
Kommission Abholung
Remmers Epoxy BS 3000 10kg/GEB
Versiegelung basaltgrau RAL7012
Kommission Abholung
Knauf Intol weiß
Innendispersionsfarbe
12,51tr/GEB O, 151tr/qm
32GEB/PAL
Abholung ab Lager
10,000
10,000
12,500
1 EIM
Gesamtbetrag €
476,75
E
E
MwSt.
19,000/0
MwSt.Betrag
76,12
137,50
209,00
54, 13
Endbetrag €
476,75
2% Skonto in 10 Tagen, 30 Tage netto
Skonto Wird nur nach besonderer Vereinbarung gewährt. Skontierfähig ist ausschließlich der Warenwert ohne Frachtanteile und Dienstleistungen.
1m Warenwert enthaltene Vorfrachten und Dienstleistungen werden mittels einer Pauschale (4% bei Strecke, 14,5% bei Lager) in Abzug gebracht.
Mit diesem Schein darf keine Warenausgabe erfolgen!
Gesamtgewicht: 21 ,OOO kg
Legende Preiseinheit (PE): E = pro 1, Z = pro 10, H = pro 100, T = pro 1000

---------------------------
OK   
---------------------------
Die Positionen der Texte lassen sich auch abgreifen.
Ahk_fan
Posts: 149
Joined: 31 Aug 2018, 14:34
Contact:

Re: ImageSearch innerhalb JPGs/PNG/TIFF

22 Apr 2021, 06:06

Hi Danke! Das kenne und nutze ich auch, das UWP ist echt schnell aber bei schlechten Scans nicht so gut wie tesseract, zur Zeit mache ich es so, dass ich bei Scans mit hoher Auflösung UWP nehme und mit schlechter Auflösung tesseract.
Das Problem bei der Sache ist, dass z.B.
- Wenn ich nach dem Endbetrag im erkannten Text suche, dann steht ja die Summe nicht nebendran, sondern irgendwo anders (unten drunter) und durch einen Strich getrennt. d.h. ich kenne eigentlich die Position des €-Betrags nicht.
Was ich aktuell mache:
Durch einen Anlernmodus zeige ich 3 Rechnungen nacheinander in einer Gui an, markiere die Bereiche (z.b: Auftrag-Nr), die relevant sind und speicher mir die Koordinaten in eine Datenbank mit einer %-Toleranz . Beim Einlesen einer PDF vom Scanner werden nur die markierten Bereiche als Bild extrahiert und per OCR überprüft, das macht das sehr viel schneller. Ausserdem habe ich dann im jeweils erkannten Bereich nur die Daten, die benötige, d.h. extrahieren Bereich mit "Auftrag-Nr" x1:1000/y1:650 / x2:1250 /y2:850--> damit habe ich auch die Nummer selbst, die ja drunter steht.
Wo ich Probleme hab ist z.B. die Endsumme, weil die Position variabel, je nachdem wieviele Positionen es gibt. Das hatte ich bisher so gelöst, dass ich die X-Koordinaten gelassen habe und die Y-Koordinaten über das ganze Bild hab laufen lassen, auch wenn z.B. die Rechnung 2-seitig war. Aber das hat viel Zeit gekostet, da ich dann entsprechend viele Bilder per OCR und auf Sinnhaftigkeit überprüft hab. Um das zu umgehen wollte ich ähnlich ImageSearch nach einem per BLOB gespeicherten Referenzbild suchen lassen und dann entsprechnend die Koordinaten ermitteln und danach den Ausschnitt erstellen lassen für das OCR.

Ich habe gestern abend gefunden:
https://github.com/MasterFocus/AutoHotkey/tree/master/Functions/Gdip_ImageSearch
und das Funktioniert echt gut!

Hier noch mein Beispiel:
TestScript.zip
(1.25 MiB) Downloaded 5 times
regards,
AHK_fan :)
https://hr-anwendungen.de
just me
Posts: 7890
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: ImageSearch innerhalb JPGs/PNG/TIFF  Topic is solved

22 Apr 2021, 07:23

Ok! Wenn man mal davon ausgeht, dass die Bildqualität der Scans ausreicht, wäre das Folgende dann hilfreich?

Code: Select all

#NoEnv
SetBatchLines, -1
Words := ocr("test_AB2_deskew.png", "de")
Gui, Add, ListView, w800 r25, #|X|Y|W|H|Text
For Line, Array In Words
   For Each, Word In Array
      LV_Add("", Line, Word*)
LV_ModifyCol()
Gui, Show, , Words
Return
GuiClose:
ExitApp
; ----------------------------------------------------------------------------------------------------------------------
ocr(file, lang := "FirstFromAvailableLanguages")
{
   static OcrEngineStatics, OcrEngine, MaxDimension, LanguageFactory, Language, CurrentLanguage, BitmapDecoderStatics, GlobalizationPreferencesStatics
   if (OcrEngineStatics = "")
   {
      CreateClass("Windows.Globalization.Language", ILanguageFactory := "{9B0252AC-0C27-44F8-B792-9793FB66C63E}", LanguageFactory)
      CreateClass("Windows.Graphics.Imaging.BitmapDecoder", IBitmapDecoderStatics := "{438CCB26-BCEF-4E95-BAD6-23A822E58D01}", BitmapDecoderStatics)
      CreateClass("Windows.Media.Ocr.OcrEngine", IOcrEngineStatics := "{5BFFA85A-3384-3540-9940-699120D428A8}", OcrEngineStatics)
      DllCall(NumGet(NumGet(OcrEngineStatics+0)+6*A_PtrSize), "ptr", OcrEngineStatics, "uint*", MaxDimension)   ; MaxImageDimension
   }
   if (file = "ShowAvailableLanguages")
   {
      if (GlobalizationPreferencesStatics = "")
         CreateClass("Windows.System.UserProfile.GlobalizationPreferences", IGlobalizationPreferencesStatics := "{01BF4326-ED37-4E96-B0E9-C1340D1EA158}", GlobalizationPreferencesStatics)
      DllCall(NumGet(NumGet(GlobalizationPreferencesStatics+0)+9*A_PtrSize), "ptr", GlobalizationPreferencesStatics, "ptr*", LanguageList)   ; get_Languages
      DllCall(NumGet(NumGet(LanguageList+0)+7*A_PtrSize), "ptr", LanguageList, "int*", count)   ; count
      loop % count
      {
         DllCall(NumGet(NumGet(LanguageList+0)+6*A_PtrSize), "ptr", LanguageList, "int", A_Index-1, "ptr*", hString)   ; get_Item
         DllCall(NumGet(NumGet(LanguageFactory+0)+6*A_PtrSize), "ptr", LanguageFactory, "ptr", hString, "ptr*", LanguageTest)   ; CreateLanguage
         DllCall(NumGet(NumGet(OcrEngineStatics+0)+8*A_PtrSize), "ptr", OcrEngineStatics, "ptr", LanguageTest, "int*", bool)   ; IsLanguageSupported
         if (bool = 1)
         {
            DllCall(NumGet(NumGet(LanguageTest+0)+6*A_PtrSize), "ptr", LanguageTest, "ptr*", hText)
            buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
            text .= StrGet(buffer, "UTF-16") "`n"
         }
         ObjRelease(LanguageTest)
      }
      ObjRelease(LanguageList)
      return text
   }
   if (lang != CurrentLanguage) or (lang = "FirstFromAvailableLanguages")
   {
      if (OcrEngine != "")
      {
         ObjRelease(OcrEngine)
         if (CurrentLanguage != "FirstFromAvailableLanguages")
            ObjRelease(Language)
      }
      if (lang = "FirstFromAvailableLanguages")
         DllCall(NumGet(NumGet(OcrEngineStatics+0)+10*A_PtrSize), "ptr", OcrEngineStatics, "ptr*", OcrEngine)   ; TryCreateFromUserProfileLanguages
      else
      {
         CreateHString(lang, hString)
         DllCall(NumGet(NumGet(LanguageFactory+0)+6*A_PtrSize), "ptr", LanguageFactory, "ptr", hString, "ptr*", Language)   ; CreateLanguage
         DeleteHString(hString)
         DllCall(NumGet(NumGet(OcrEngineStatics+0)+9*A_PtrSize), "ptr", OcrEngineStatics, ptr, Language, "ptr*", OcrEngine)   ; TryCreateFromLanguage
      }
      if (OcrEngine = 0)
      {
         msgbox Can not use language "%lang%" for OCR, please install language pack.
         ExitApp
      }
      CurrentLanguage := lang
   }
   if (SubStr(file, 2, 1) != ":")
      file := A_ScriptDir "\" file
   if !FileExist(file) or InStr(FileExist(file), "D")
   {
      msgbox File "%file%" does not exist
      ExitApp
   }
   VarSetCapacity(GUID, 16)
   DllCall("ole32\CLSIDFromString", "wstr", IID_RandomAccessStream := "{905A0FE1-BC53-11DF-8C49-001E4FC686DA}", "ptr", &GUID)
   DllCall("ShCore\CreateRandomAccessStreamOnFile", "wstr", file, "uint", Read := 0, "ptr", &GUID, "ptr*", IRandomAccessStream)
   DllCall(NumGet(NumGet(BitmapDecoderStatics+0)+14*A_PtrSize), "ptr", BitmapDecoderStatics, "ptr", IRandomAccessStream, "ptr*", BitmapDecoder)   ; CreateAsync
   WaitForAsync(BitmapDecoder)
   BitmapFrame := ComObjQuery(BitmapDecoder, IBitmapFrame := "{72A49A1C-8081-438D-91BC-94ECFC8185C6}")
   DllCall(NumGet(NumGet(BitmapFrame+0)+12*A_PtrSize), "ptr", BitmapFrame, "uint*", width)   ; get_PixelWidth
   DllCall(NumGet(NumGet(BitmapFrame+0)+13*A_PtrSize), "ptr", BitmapFrame, "uint*", height)   ; get_PixelHeight
   if (width > MaxDimension) or (height > MaxDimension)
   {
      msgbox Image is to big - %width%x%height%.`nIt should be maximum - %MaxDimension% pixels
      ExitApp
   }
   BitmapFrameWithSoftwareBitmap := ComObjQuery(BitmapDecoder, IBitmapFrameWithSoftwareBitmap := "{FE287C9A-420C-4963-87AD-691436E08383}")
   DllCall(NumGet(NumGet(BitmapFrameWithSoftwareBitmap+0)+6*A_PtrSize), "ptr", BitmapFrameWithSoftwareBitmap, "ptr*", SoftwareBitmap)   ; GetSoftwareBitmapAsync
   WaitForAsync(SoftwareBitmap)
   DllCall(NumGet(NumGet(OcrEngine+0)+6*A_PtrSize), "ptr", OcrEngine, ptr, SoftwareBitmap, "ptr*", OcrResult)   ; RecognizeAsync
   WaitForAsync(OcrResult)
   DllCall(NumGet(NumGet(OcrResult+0)+6*A_PtrSize), "ptr", OcrResult, "ptr*", LinesList)   ; get_Lines
   DllCall(NumGet(NumGet(LinesList+0)+7*A_PtrSize), "ptr", LinesList, "int*", count)   ; count
   WordArray := []
   loop % count
   {
      LineIndex := A_Index
      DllCall(NumGet(NumGet(LinesList+0)+6*A_PtrSize), "ptr", LinesList, "int", A_Index-1, "ptr*", OcrLine)
      DllCall(NumGet(NumGet(OcrLine+0)+6*A_PtrSize), "ptr", OcrLine, "ptr*", WordsList)   ; get_Words
      DllCall(NumGet(NumGet(WordsList+0)+7*A_PtrSize), "ptr", WordsList, "int*", WordsCount)   ; Words count
      loop % WordsCount
      {
         DllCall(NumGet(NumGet(WordsList+0)+6*A_PtrSize), "ptr", WordsList, "int", A_Index-1, "ptr*", OcrWord)
         VarSetCapacity(RECT, 16, 0)
         DllCall(NumGet(NumGet(OcrWord+0)+6*A_PtrSize), "ptr", OcrWord, "ptr", &RECT)   ; get_BoundingRect
         DllCall(NumGet(NumGet(OcrWord+0)+7*A_PtrSize), "ptr", OcrWord, "ptr*", hText)   ; get_Text
         buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
         x := NumGet(&RECT, 0, "float")
         y := NumGet(&RECT, 4, "float")
         w := NumGet(&RECT, 8, "float")
         h := NumGet(&RECT, 12, "float")
         text := StrGet(buffer, "UTF-16")
         WordArray[LineIndex, A_Index] := [x, y, w, h, text]
         ObjRelease(OcrWord)
      }
      ObjRelease(WordsList)
      ObjRelease(OcrLine)
   }
   Close := ComObjQuery(IRandomAccessStream, IClosable := "{30D5A829-7FA4-4026-83BB-D75BAE4EA99E}")
   DllCall(NumGet(NumGet(Close+0)+6*A_PtrSize), "ptr", Close)   ; Close
   ObjRelease(Close)
   Close := ComObjQuery(SoftwareBitmap, IClosable := "{30D5A829-7FA4-4026-83BB-D75BAE4EA99E}")
   DllCall(NumGet(NumGet(Close+0)+6*A_PtrSize), "ptr", Close)   ; Close
   ObjRelease(Close)
   ObjRelease(IRandomAccessStream)
   ObjRelease(BitmapDecoder)
   ObjRelease(BitmapFrame)
   ObjRelease(BitmapFrameWithSoftwareBitmap)
   ObjRelease(SoftwareBitmap)
   ObjRelease(OcrResult)
   ObjRelease(LinesList)
   return WordArray
}
; ----------------------------------------------------------------------------------------------------------------------
CreateClass(string, interface, ByRef Class)
{
   CreateHString(string, hString)
   VarSetCapacity(GUID, 16)
   DllCall("ole32\CLSIDFromString", "wstr", interface, "ptr", &GUID)
   result := DllCall("Combase.dll\RoGetActivationFactory", "ptr", hString, "ptr", &GUID, "ptr*", Class, "uint")
   if (result != 0)
   {
      if (result = 0x80004002)
         msgbox No such interface supported
      else if (result = 0x80040154)
         msgbox Class not registered
      else
         msgbox error: %result%
      ExitApp
   }
   DeleteHString(hString)
}
; ----------------------------------------------------------------------------------------------------------------------
CreateHString(string, ByRef hString)
{
    DllCall("Combase.dll\WindowsCreateString", "wstr", string, "uint", StrLen(string), "ptr*", hString)
}
; ----------------------------------------------------------------------------------------------------------------------
DeleteHString(hString)
{
   DllCall("Combase.dll\WindowsDeleteString", "ptr", hString)
}
; ----------------------------------------------------------------------------------------------------------------------
WaitForAsync(ByRef Object)
{
   AsyncInfo := ComObjQuery(Object, IAsyncInfo := "{00000036-0000-0000-C000-000000000046}")
   loop
   {
      DllCall(NumGet(NumGet(AsyncInfo+0)+7*A_PtrSize), "ptr", AsyncInfo, "uint*", status)   ; IAsyncInfo.Status
      if (status != 0)
      {
         if (status != 1)
         {
            DllCall(NumGet(NumGet(AsyncInfo+0)+8*A_PtrSize), "ptr", AsyncInfo, "uint*", ErrorCode)   ; IAsyncInfo.ErrorCode
            msgbox AsyncInfo status error: %ErrorCode%
            ExitApp
         }
         ObjRelease(AsyncInfo)
         break
      }
      sleep 10
   }
   DllCall(NumGet(NumGet(Object+0)+8*A_PtrSize), "ptr", Object, "ptr*", ObjectResult)   ; GetResults
   ObjRelease(Object)
   Object := ObjectResult
}
Ahk_fan
Posts: 149
Joined: 31 Aug 2018, 14:34
Contact:

Re: ImageSearch innerhalb JPGs/PNG/TIFF

22 Apr 2021, 07:39

surprised.gif
surprised.gif (661.2 KiB) Viewed 126 times
Wahnsinn, das bringt mich einen Riesenschritt vorwärts. Jetzt kann ich die Daten noch besser mit den Daten vom ERP-System abgleichen. DANKE!!!! :bravo:
Diese Ausgabe kann man sehr gut auch mit den Boxen in tesseract vergleichen, da gibt es auch solche Ausgaben.
regards,
AHK_fan :)
https://hr-anwendungen.de
BoBo
Posts: 4598
Joined: 13 May 2014, 17:15

Re: ImageSearch innerhalb JPGs/PNG/TIFF

22 Apr 2021, 08:33

Wenn ich nach dem Endbetrag im erkannten Text suche, dann steht ja die Summe nicht nebendran, sondern irgendwo anders (unten drunter)
Hab ich mich auch gefragt. Selbst nach Erkennungsrate bei einem identischen Formular kann ein Suchbegriff eben unterschiedlich positioniert sein, und bei monetär-relevanten Sachverhalten kann sowas dann ins Geld gehen :shifty: Wenn du allerdings ausschließlich soft-copies aus einer einzigen Quelle prozessierst dürften die Probleme beherrschbar sein, da die Suchmuster deckungsgleich sind und sich ggf sogar anlernen lassen. Good luck :)

Return to “Ich brauche Hilfe”

Who is online

Users browsing this forum: Bing [Bot] and 3 guests