Pesquisar Subimagem dentro de Imagem
02/01/2024
0
André Miranda
Posts
02/01/2024
Arthur Heinrich
A técnica para isso é comparar a sua imagem "padrão" com outra imagem de mesmo formato, utilizando o produto escalar entre dois vetores.
Imagine que sua imagem 5x5 é um vetor de 75 dimensões (25 pixels com 3 componentes cada um RGB).
Você precisa normalizar o seu vetor, dividindo cada componente/dimensão pelo comprimento do vetor, de forma a obter um vetor de comprimento unitário. Supondo "v" um vetor dinâmico de 75 posições, de 0 a 74, contendo os componentes RGB de cada pixel, para normalizá-lo, você faz:
d:=0; for i:=0 to Pred(v) do d:=d+sqr(v[i]); d:=sqrt(d); for i:=0 to Pred(v) do v[i]:=v[i]/d;
Depois de normalizar o vetor da sua imagem padrão e da porção de tela que você quer comparar, você executa o produto escalar destes dois vetores, para checar a semelhança entre eles:
v:=0; for i:=0 to Pred(v1) do v:=v+v1[i]*v2[i];
O resultado dessa conta será um número real "v", tal que: -1 <= v <= 1
Se o v se aproximar de 0, as imagens são totalmente diferentes. Se ele se aproximar de 1, as imagens serão semelhantes.
Dois vetores idênticos, mas em direções opostas produzirão um valor -1. Portanto, para efeito de análise, podemos utilizar abs(v).
Aí, basta estipular um threshold de tolerância. Digamos que valores de abs(v) >= 0,9 seja considerado igual. Você pode experimentar valores diferentes, de forma a ser mais ou menos tolerante.
Ao normalizar os vetores, eliminamos a luminosidade da comparação. Se considerarmos 3 quadrados, sendo um preto, outro cinza e um branco, ambos são idênticos do ponto de vista do padrão. Mas não são iguais a quadrados de cores distintas, que não façam parte da escala de cinza, onde os componentes RGB tem a mesma proporção.
A única situação em que não é possível executar este procedimento é quando um vetor possui dimensão zero. É o caso particular de um quadrado preto, onde o RGB é (0,0,0). Mas, se quiser, pode introduzir um pequeno erro, somando um resíduo em cada componente, de forma que, ao invés de capturar o RGB dentro de valores de 0 a 255, você obtenha, por exemplo 0,001 a 255,001. Este erro provavelmente não vai interferir significativamente na análise, mas vai permitir a comparação de uma imagem composta somente de preto (0,0,0).
Em uma situação real, pode ser que as imagens a serem comparadas não sejam do mesmo tamanho (resolução) ou estejam rotacionadas. Nestas situações, é necessário primeiro girar a imagem, recortar a porção desejada e redimensiona-la para a dimensão do padrão, antes que possa ser comparada.
02/01/2024
Arthur Heinrich
02/01/2024
André Miranda
function ImageSearch(const SubImageFile: String): TRect;
var
X, Y: Integer;
SubimageColor: TColor;
ScreenBitmap: TBitmap;
SubImageBitmap: TBitmap;
begin
Result := Rect(-1, -1, -1, -1);
if not FileExists(SubImageFile) then
Exit;
ScreenBitmap := TBitmap.Create;
SubImageBitmap := TBitmap.Create;
try
SubImageBitmap.LoadFromFile(SubImageFile);
if (SubImageBitmap.Height < 3) or (SubImageBitmap.Width < 3) then
Exit; // Acima de 3 px
ScreenBitmap.PixelFormat := pf24bit; // 24 bits
ScreenBitmap.Width := Screen.Width;
ScreenBitmap.Height := Screen.Height;
BitBlt(ScreenBitmap.Canvas.Handle, 0, 0, ScreenBitmap.Width, ScreenBitmap.Height, GetDC(0), 0, 0, SRCCOPY);
for X := ScreenBitmap.Width div 2 to ScreenBitmap.Width - SubImageBitmap.Width do
begin
for Y := ScreenBitmap.Height div 3 to ScreenBitmap.Height - SubImageBitmap.Height do
begin
{for Y := ScreenBitmap.Height - SubImageBitmap.Height downto 0 do
begin
for X := ScreenBitmap.Width - SubImageBitmap.Width downto 0 do
begin}
SubimageColor := SubImageBitmap.Canvas.Pixels[0, 0];
if (ScreenBitmap.Canvas.Pixels[X, Y] = SubimageColor) and
(ScreenBitmap.Canvas.Pixels[X + SubImageBitmap.Width - 1, Y] = SubImageBitmap.Canvas.Pixels[SubImageBitmap.Width - 1, 0]) and
(ScreenBitmap.Canvas.Pixels[X, Y + SubImageBitmap.Height - 1] = SubImageBitmap.Canvas.Pixels[0, SubImageBitmap.Height - 1]) and
(ScreenBitmap.Canvas.Pixels[X + SubImageBitmap.Width - 1, Y + SubImageBitmap.Height - 1] = SubImageBitmap.Canvas.Pixels[SubImageBitmap.Width - 1, SubImageBitmap.Height - 1]) then
begin
Result := Rect(X, Y, X + SubImageBitmap.Width, Y + SubImageBitmap.Height);
Exit;
end;
end;
end;
//ShowMessage('Imagem não encontrada na tela.');
finally
ScreenBitmap.Free;
SubImageBitmap.Free;
end;
end;
procedure Teste;
var
Rect1: TRect;
Center: TPoint;
begin
Rect := ImageSearch('Teste1.bmp'); // 24 bits
if (Rect2.Left <> -1) and (Rect2.Top <> -1) then
begin
// Encontrar o ponto central da posição
Center.X := (Rect2.Left + Rect2.Right) div 2;
Center.Y := (Rect2.Top + Rect2.Bottom) div 2;
SetCursorPos(Center.X, Center.Y);
Sleep(100);
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
Sleep(100);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
end;
end;
03/01/2024
Arthur Heinrich
Vi que você utilizou "GetDC(0)" para obter o handle da tela e poder executar a cópia (bitblt) para o seu TBitmap.
Se não me engano, quando você faz isso, o handle fica associado à sua aplicação e você precisa liberar o handle depois de utilizar, usando ReleaseDC(...).
04/01/2024
André Miranda
04/01/2024
Arthur Heinrich
04/01/2024
André Miranda
Clique aqui para fazer login e interagir na Comunidade :)