Pesquisar Subimagem dentro de Imagem
Tudo bem? Gostaria de uma mão em algo que estou fazendo: Tenho um bitmap 5x5 de uma cor solid especifica e pesquiso ele em uma area especifica do desktop. O problema é o seguinte: Não funciona 100%, as vezes essa "area" aparece com um tipo de transparência e muda a cor de alguns px e a minha função não acha. Estou batendo cabeça pra tentar resolver e como não tenho experiencia nesse tipo de aplicação estou dando voltas e não saio do lugar! Derrepente se alguém puder só me clarear com uma idéia do que posso fazer! Desde já agradeço.
André Miranda
Curtidas 0
Respostas
Arthur Heinrich
02/01/2024
O reconhecimento de um padrão é muito utilizado. Nas máquinas de auto atendimento, onde colocamos notas de dinheiro, a máquina utiliza esse tipo de reconhecimento, para identificar se o que foi introduzido é dinheiro verdadeiro e o valor, apesar de o dinheiro muitas vezes estar sujo e rasurado.
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:
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:
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.
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.
GOSTEI 0
Arthur Heinrich
02/01/2024
Onde se lê "Pred(v)" eu pretendia escrever "Pred(Length(v))" ou "High(v)", para indicar o último elemento.
GOSTEI 0
André Miranda
02/01/2024
Beleza Arthur? Valeu pelo feedback, vou estudar e tentar entender essa questão e testar! Vou aproveitar e postar o code.
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;
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;
GOSTEI 0
Arthur Heinrich
02/01/2024
Um comentário sobre seu código.
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(...).
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(...).
GOSTEI 0
André Miranda
02/01/2024
Opa, Arthur, valeu pelo toque passou despercebido. Mas já abandonei esse código, não deu certo para a minha finalidade a imagem buscada nem sempre aparece com os mesmos pixels, aparece com alpha e isso dai modifica os pixels, dependendo da abordagem da muito falso positivo, e se apertar demais na % de proximidade não acha. Não achei um meio termo. Enfim, dei uma parada pra refrescar um pouco a cabeça e tentar de novo posteriormente. Obrigado!
GOSTEI 0
Arthur Heinrich
02/01/2024
Se quiser pesquisar na internet por um algoritmo pronto, não procure por comparação de imagens, pois a maioria dos casos vai comparar exatamente, que é o caso em que falha para você. Procure por similaridade de imagens.
GOSTEI 0
André Miranda
02/01/2024
Cara, nem os prontos, nem os que eu fiz, nada resolveu. É dificil buscar precisão quando o que você quer não é preciso. O botão aparecer de várias formas na tela, por ter transparêcia e tal, teria que criar um padrão, mas não tem como porque com o alpha o que tem atrás atrapalha a precisão modificando os pixels. Quando não tem alpha até da pra ter padrão porque é um botão com fundo rbg(0, 0, 0), e a cor da fonte (255, 255, 255) isso é um padrao, mas nem sempre o botão aparece assim. Sinceramente estou bem desanimado kkkk por isso nunca vi nada do tipo, porque deve ser impossível. rsrsr
GOSTEI 0