SRL Archive - SRL Member Goodies

Quick Corner Detection


SKy Scripter

2012-04-18 15:36:45

SHAPES THAT HAVE BEEN DETECTED





So i was bored and decided to put together a corner detection script

Directions:
1.) make any shape make sure the background is (WHITE) 256^3
2.) make the dimensions of the image no more than 400x300 and center the
object.
3.) load image, hit corner detect, and watch it do magic :)




The way it works.
1.) scans for an edge then gets the edges by using a flood fill algorithm that only follows the edge path.

2.) it scans through each edge pixel and gets the two furthest edge points within the distance of 15 pixels and measures the angle using ATan2(y, x)

[ATTACH]15469[/ATTACH]

3.) measures the angle and defines whether of not its a line or a corner.



please feel free to look and learn :)

DOWNLOAD: [ATTACH]15470[/ATTACH]


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Fast_Corner_Detect
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

Bitmap bmp;
int w = 0;
int h = 0;
int[,] c;
bool[,] cn, bc;
int area, param;




int clred = ColorTranslator.ToWin32(Color.Red);
int clpurple = ColorTranslator.ToWin32(Color.Purple);
int clblue = ColorTranslator.ToWin32(Color.Blue);
int clwhite = ColorTranslator.ToWin32(Color.White);

int[] cx = new int[8] { 1, 0, -1, 0, 1, 1, -1, -1 };
int[] cy = new int[8] { 0, 1, 0, -1, -1, 1, 1, -1 };



void edgefill(int x, int y)
{
area++;
c[x, y] = clblue;

cn[x, y] = true;


for (int i = 0; i < 8; i++)
{
int a = x + cx[i];
int b = y + cy[i];
if ((c[a, b] < clwhite) && (cn[a, b] == false))
for (int j = 0; j < 8; j++)
{
if (c[a + cx[j], b + cy[j]] == clwhite) edgefill(a, b);
}

}


}

int sx, sy;
double adist, filldist, maxd;
Point es, ee;

void ffillc(int x, int y)
{
bc[x, y] = true;
area++;
for (int i = 0; i < 8; i++)
if (bc[x + cx[i], y + cy[i]] == false)
if (cn[x + cx[i], y + cy[i]] == true)
{
int a = x + cx[i];
int b = y + cy[i];
double sqrX = Math.Pow(a - sx, 2);
double sqrY = Math.Pow(b - sy, 2);
adist = Math.Sqrt(sqrX + sqrY);
if (adist < filldist)
{
ffillc(a, b);

}
else
{
if (es.IsEmpty)
// start the max dist for angle
es = new Point(a, b);


sqrX = Math.Pow(es.X - a, 2);
sqrY = Math.Pow(es.Y - b, 2);
adist = Math.Sqrt(sqrX + sqrY);
if (maxd < adist)
{
maxd = adist;
ee = new Point(a, b);
}
}

}


}

public double convertdeg(double deg)
{

// deg = Math.Atan2(y - j, x - i);

double pi2 = Math.PI * 2;
while (deg < pi2) deg = deg + pi2;
while (deg > pi2) deg = deg - pi2;

deg = deg * (180 / Math.PI);
// deg = Math.Abs(deg - 180);

return deg;


}


private void button1_Click(object sender, EventArgs e)
{
// Label edgelb;

// Quick Edge Find

// []
//
// []
//
bool fillit = false;

for (int y = 2; y < h - 2; y++)
for (int x = 2; x < w - 2; x++)
if (c[x, y] < clwhite)
{
if (fillit == false)
{

edgefill(x, y);
fillit = true;
}

}


filldist = 15; // distance to scan
for (int y = 2; y < h - 2; y++)
for (int x = 2; x < w - 2; x++)

if (cn[x, y] == true)
{
bc = new bool[w, h];
sx = x;
sy = y;
es = new Point();
ee = new Point();
maxd = 0;
ffillc(x, y);

// double deg = Math.Atan2(y - j, x - i);

double angle1 = convertdeg(Math.Atan2(y - ee.Y, x - ee.X));
double angle2 = convertdeg(Math.Atan2(y - es.Y, x - es.X));
double realangle = Math.Abs(angle2 - angle1);
realangle = Math.Abs(realangle - 180);
// richTextBox1.AppendText("ANGLE: " + realangle.ToString() + "\n");
if (realangle > 42) c[x, y] = clred; // this is where it finds that its an angle





}


for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++)
{

bmp.SetPixel(x, y, ColorTranslator.FromWin32(c[x, y]));

}



Graphics g;
g = pictureBox1.CreateGraphics();
g.DrawImage(bmp, 0, 0);



}


// int[] hist;





private void button2_Click(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
bmp = new Bitmap(openFileDialog1.FileName);

w = bmp.Width;
h = bmp.Height;
// richTextBox1.AppendText("Width: " + w.ToString() + "Height: " + h.ToString());
c = new int[w, h];
cn = new bool[w, h];

for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++)
{
c[x, y] = ColorTranslator.ToWin32(bmp.GetPixel(x, y));
}

pictureBox1.ImageLocation = openFileDialog1.FileName;


}

}

private void pictureBox1_Click(object sender, EventArgs e)
{



}

private void pictureBox1_DoubleClick(object sender, EventArgs e)
{

}

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{

}
}
}

nielsie95

2012-04-18 15:44:04

Pretty cool!
Unfortunately, the box random is deprecated :p

SKy Scripter

2012-04-18 15:54:07

From: nielsie95
Pretty cool!
Unfortunately, the box random is deprecated :p


haha i know XD


well if for some reason runescape does more shapes hit me up lol
because pups shape detection is limited to more congruent shapes.
i think mines faster too? i havent actually checked but looking at the coding mines less repetitive.

Gala

2012-04-18 18:28:29

pretty cool.


:)
works nice.


I tried some edge detection, but I failed. (wanted to use similar algorithms as PS does, but for this kind of stuff they are too slow I think)

m34tcode

2012-04-18 22:17:57

Wow, this could really own for minimap area detection. A tpoint array of corners compared to a map of corners? pro

SKy Scripter

2012-04-19 01:03:03

[ATTACH]15481[/ATTACH]


Gets curves and corners… so yeah i guess if you were looking for a corner on
a mini maap :P
or maybe you count 9 corners to know if you are where you need to be on the mini map

m34tcode

2012-04-19 01:27:37

i was thinking count, but that would return false positives. Instead it could search like object dtm does. Seems like it would be very accurate, especially if combined with dtms and tpas

Ill try writing this tomoro night. Ill paste the converted func, along with the matching func if i finish, if not ill post what i have =]\

Edit: partially finished. started late =\

weequ

2012-04-26 03:06:15

Very nice. I love the idea of using flood fill to only follow the edge. And yeah I can imagine this working for minimap walking as m34tcode said. ObjDTM's -> CornerDTM's (No minimap objects required)

Edit: I might try to make a positioning system based on this. Starting with my pest controller script that needs some navigation for portal killing :p

m34tcode

2012-04-27 00:19:01

I have already written a ton. the corner detection func is all thats really left, and its near finished.

{$i srl/srl.simba}

function GetWallsArr : array of TBoolArray;
var
WallColor : TColor;
x,y : Integer;
begin
WallColor := 16050927;

SetArrayLength(Result,MMX2-MMX1);
//TODO set start x and endx to a trig calculation, possibly improving performance
for x := 0 to MMX2-MMX1-1 do
begin
SetArrayLength(Result[x],MMY2-MMY1);
for y := 0 to MMY2-MMY1-1 do
begin
Result[x][y] := SimilarColors(GetColor(MMX1+x,MMY1+y),WallColor,25);
end;
end;
end;

function GetAngleBetweenPoints(P1,P2 : TPoint) : Extended;
begin
Result := ArcTan((P2.y-P1.y)/(P2.x-P1.x));
end;

function RotPoint(Point : TPoint; x,y : Integer) : TPoint;
var
angle : Integer;
newPoint : TPoint;
begin
angle := 0;
newPoint := Point;
while (newPoint.x = Point.x) and (newPoint.y = Point.y) do
begin
newPoint := RotatePoint(Point,Radians(angle),x,y);
angle := angle + 1;
end;
result := newPoint;
end;

function GetWallAngleAtPos(x,y : Integer; bools : array of TBoolArray; AngleDist : Integer) : Extended;
var
translatedPoint,ref1,ref2 : TPoint;
I : Integer;
begin
translatedPoint := Point(x+AngleDist,y);
while not (GetAngleBetweenPoints(Point(x,y),translatedPoint) = 0) do
begin
translatedPoint := RotPoint(translatedPoint,x,y);
if (bools[translatedPoint.x][translatedPoint.y]) then
begin
if (ref1.x == 0) and (ref1.y == 0) then
ref1 := translatedPoint
else
ref2 := translatedPoint;
end;
end;
result := abs(GetAngleBetweenPoints(Point(x,y),ref1)-GetAngleBetweenPoints(Point(x,y),ref2));
end;

function GetWallCorners(AngleDist : Integer; bools : array of TBoolArray) : TPointArray;
var
x,y : Integer;
begin
SetArrayLength(Result,0);
for x := 0 to length(bools)-1 do
begin
for y := 0 to length(bools[0])-1 do
begin
if InRange(Round(getWallAngleAtPos(x,y,bools,AngleDist)),60,120) then
begin
SetArrayLength(Result,length(Result)+1);
Result[High(Result)] := Point(x,y);
end;
end;
end;
end;

var xx,yy,bmp : Integer; bools : array of TBoolArray;
begin
SetupSRL;
//test bool arr
bools := GetWallsArr;

writeln(GetAngleBetweenPoints(Point(1,5),(Point(2,5))));
GetWallAngleAtPos(mmcx,mmcy,bools,25);
while true do
begin
GetMousePos(MouseLoc.x,MouseLoc.y)
end;
{for xx := 0 to MMX2-MMX1-1 do
begin
for yy := 0 to MMY2-MMY1-1 do
begin
if bools[xx][yy] then
begin
MoveMouse(MMX1+xx,MMY1+yy);
wait(10);
end;
end;
end; }
end.

work in progress, not finished yet. the rotate pixel func, will rotate exactly one pixel, so for smaller pixels the actual rotation per call will be larger than larger circles.

SKy Scripter

2012-05-03 02:33:54

thats good code :)

m34tcode

2012-05-03 14:50:45

Iv been busy and not been able to write anything further than that so far =[

Sometime soon i may be able to get back to it, if noone else wants to finish it.

bg5

2013-09-28 15:16:44

Hey SKy Scripter , thanks for sharing this code.
I've translated it to Simba and modified a little bit.

Speed:

Small Bitmap:
Lape: 350 ms
PascalScript: 26000 ms
Big bitmap:
Lape: 10000 ms
PascalScript: press 'run' and go sleep


program new;
{$i srl/srlLape.simba}


var w,h,i,sx,sy : integer;
C :t2dIntegerArray;
cn ,bc :array of array of boolean;
cx,cy : TintegerArray;
adist, filldist, maxd : double;
es,ee : TPoint;

var Timer,bmp : integer;

procedure ffillc(x,y : integer);
var i ,a,b: integer;
var sqrX ,sqrY : double;
begin

bc[x][y] := true;
for i:=0 to 7 do
if (not bc[x+cx[i]][y+cy[i]] )and cn[x+cx[i]][y+cy[i]] then
begin
a := x + cx[i];
b := y + cy[i];
sqrX := Pow(a - sx ,2);
sqrY := Pow(b - sy ,2);
adist := Sqrt(sqrX + sqrY);
if adist < filldist then
ffillc(a,b)
else
begin
if (es.x =0) and (es.y = 0) then
es := Point(a,b);

sqrX := Pow(es.x -a, 2);
sqrY := Pow(es.y -b, 2);
adist := Sqrt(sqrX + sqrY);
if maxd < adist then
begin
maxd := adist;
ee := Point(a,b);
end;
end;
end;

end;

procedure edgefill(x,y :integer);
var i,j,a,b:integer;
begin

c[x][y] := clblue;
cn[x][y] := true;
for i:=0 to 7 do
begin

a := x + cx[i];
b := y + cy[i];
if ( (a<w)and(b<h) ) and (c[a][b] < clwhite) and (not cn[a][b]) then
for j:=0 to 7 do
if (c[a + cx[j]][ b + cy[j]] = clwhite) then
edgefill(a, b);
end;
end;

{function ConvertDeg ( deg:double) : double;
var pi2 : double;
begin
pi2 := Pi*2;
while (deg < pi2) do deg := deg+ pi2;
while (deg > pi2) do deg := deg- pi2;
deg := deg*(180/Pi);
Result := deg;
end; }

var crnTPA : TPointArray;

procedure Init;
var x,y,i,j : integer;
angle1 ,angle2 ,realangle : double;
begin

for y:= 1 to h-1 do
for x:= 1 to w-1 do
if (c[x][y] < clwhite) then
begin
edgefill(x,y);

break;
end;

filldist := 15; // distance to scan

for y:= 1 to h-2 do
for x:= 1 to w-2 do
if cn[x][y] then
begin

for i:= 0 to h-2 do //clearing bc
for j:= 0 to w-2 do
bc[j][i] := FALSE;


sx := x;
sy := y;
es := Point(0,0);
ee := Point(0,0);
maxd := 0;
ffillc(x,y);

angle1 := degrees( ArcTan2(y-ee.y , x - ee.x));
angle2 := degrees( ArcTan2(y-es.y , x - es.x));
realangle := abs(angle2 - angle1);
realangle := abs(realangle - 180);

if realangle > 42 then
begin
//AppendTPA(crnTPA,[Point(x,y)]);
c[x][y] := clred;
end;
end;

for i := 0 to w-1 do
for j:= 0 to h-1 do
FastSetPixel(bmp,i,j,c[i][j]);

//DrawTPABitmap(bmp,crnTPA,clred);
end;

procedure setup(bmp:integer);
var TPA :Tpointarray;
begin
GetBitmapSize(bmp,w,h);
c := GetBitmapAreaColors(bmp,0,0,w-1,h-1);


cx := [ 1, 0, -1, 0, 1, 1, -1, -1 ];
cy := [ 0, 1, 0, -1, -1, 1, 1, -1 ];

SetLength(cn,w);
for i := 0 to w-1 do
SetLength(cn[i],h);

SetLength(bc,w);
for i := 0 to w-1 do
SetLength(bc[i],h);

end;


begin
{bmp := BitmapFromString(119, 151, 'beNrt1LENgEAMBEFKp3M' +
'gIiT789vMyhWMTr4uSX07zuM5DquF3wMSQEadEUadROYcEEadREa' +
'dEeacEeYcQ0adEeYcQ0adEeYcQ+YcEEYNmTBhwpAJQyZMGDJhMyY' +
'MmTBhyIQJN0IGS7g7MlUzJgyZMGHIhAnvj4yUsEcBmTDh8cg8CUM' +
'mTLgcGSZhwpAhEx6PTJJwd2SMZmzGhAkTHo/M0IzNmDDhcmSAZkw' +
'YMmHC45HpmbEZEyZcjozOjAlDJkx4PDI3MzZjwoQJQxbh/ZGhEYZ' +
'MmHA5MjHChCFDJjweGRdhyIQJEx6PzGo1MigzJgyZMGHIIrw/MiX' +
'C3ZERmTFhESYMWYT3R+ZDmLAgEx6PDIcwZBEuRyZDmLAglyNjIUx' +
'Y38hMCEMW4XJkIIQ9Cpkx4fHINFY7c5AkSdKvuwFnhmfS'); }
{ bmp := BitmapFromString(98, 73, 'beNrt20EOAkEMA0H//9Phh' + //small bitmap
'MRpJAS7m8lUP6HOdpU0u7xDsfb5DMsCB9e3Psda5bfgnMyVK+Mz3' +
'ipPBGcGV/rFZxer7BafVlaZEp9HrDI9RPdAUaJEiRIlSpQoUaJEi' +
'RIlSpQoUaJEiRIlSpQoUaJEiRIlSpQoUaJEiRKlzkoWg6ByzVgXj' +
'lU8qzikNMbx/PIinG1VGwaniVXNis+dXHVSfP5uVVpawVlwoZCO6' +
'wW8YyDo'); }
bmp := BitmapFromString(227, 247, 'beNrt3dFxwgAQQ0H33zT' + //big bitmap
'5oIUbIln7StAticNk4PORJEmS9Pqe5zGCQJVOlH4zhUCVQBWoUpR' +
'SVgWqBKrWlIKqFqisClTpSimoaoHKqkCVQNWUUlDVApVVgSpdKQV' +
'VoEqHUFlVhVJQBap0CJVVgSpdKQVVoEqHUFlVhVJQBap0CJVVVSg' +
'FVaBKoGoNKquqUAqqQJVA1ZRSVvUv5H6cw8H27mhhlVKxSimulIp' +
'VRMUqpaxSKlYpFa6UsoqoWKVUC1bdl1VKhSuler1Vd2SVUrFKqRa' +
'suhqulIpVRPV6q66jfKvuonyrLqJ8qM6hcKgOoYofp24hT6eilFU' +
'NQmVVFUpBVYVSVtWilFX57z6BCqoGlbJKKauilFWBKkpZFaWsakQ' +
'pq6CCKkpZdWJ5uVlSFb8UjKmWpxd7quUZ26qq+EvQsGp5y8K2ylf' +
'KqlqUgqoWqKyqQimralEKqlqgsqoKpayqRSmralEKqlqgsqoKpay' +
'qRSmoaoHKqiqUsqoWpaCqBSqrqlDKqlqUgqqn52MxXIpSVkUpqFq' +
'DyiqlrIpSUDUIlVVKWRWloGoQKquUsipKWdWgUlBBZVWUsqpBpaC' +
'CyqooZVXP5DdaOjqorIpSVikFVaCyKkopZRVUgUqpWKWUVVAFKqV' +
'ilVJWQRWolGrHqvuyCqpApVQjVt2UVUrFKqgageqOrFIqVkHVCFS' +
'3Y5VSsQqqRqC6F6uUilVQNQLVjZRv1XVUYdVplA/VXZRv1UVUYdU' +
'5lA/VLZRv1RVUYdUJlA/V/sq3annlW7W5KqwaXPlQra18q3ZWhVU' +
'jKx+qhZVv1baqsGpY5UO1qvKt2lMVVo2pfKiWVO87AD5iyInFKqi' +
'gUipWKRWrlLIKqkClVINW3Y5VSsUqqBqB6l6sUipWKdWIVQdSvlW' +
'nUT5Ud1G+VRdRhVXnUL5Vh1A+VFdQvlX7q8Kq8ZVv1ezKh2pz5Vu' +
'1tiqsmlr5Vo2sfKvmVT5U26rCqmGVb9WkyrdqTOVDtaQqrJpR+VY' +
'NqHyrplO+VaMJVOnEqrmUb9VQyrdqIoEqnVg1jvKtmkX5Vg0iUKU' +
'Tq6ZQvlUjSJIkSZIkSZIkSZIkSZIkSXH9AYkVDhM=');
Timer := GetTimeRunning;

Setup(bmp);
Init();

writeln('Time: '+tostr(GetTimeRunning-Timer));

ClearDebugImg();
//DebugBitmap(bmp);
DisplayDebugImgWindow(300,300);
DrawBitmapDebugImg(bmp);
end.

It still works great,but speed isn't satisfactory… so I've packed it into Free Pascal Plugin.

download:
[ATTACH]22252[/ATTACH]

source:

Plugin's function doesn't draw on bitmap ,but returns TPA of marked corners (red color).

Speed:
Small Bitmap:
Plugin: 15 ms
Big Bitmap
Plugin: 300ms

Testing script for plugin:
[ATTACH]22253[/ATTACH]


I have several uses for it,but well… it's not all I need.
I wonder if it is possible to get raw apexes of corners?

Olly

2013-10-05 16:37:05

Been waiting for someone to do this for years… :)

Flight

2013-12-01 22:54:57

Thanks for your work on converting this and making a plugin, bg5. I've tried using this on the RS mini-map (via SMART) and the actual 'CornerDetection()' procedure is giving me access violation errors. Any thoughts on what might be going wrong here? Here's what I'm trying to use:

program new;
{$loadlib corn_det}
{$DEFINE SMART}

{$i SRL-OSR/SRL.Simba}
{$i SRL-OSR/SRL/misc/debug.simba}

var
bmp,h,w : Integer;
tpa : TPointArray;
cols : T2DIntArray;

begin
setupSRL();
bmp := bitmapFromClient(MMX1, MMY1, MMX2, MMY2);
GetBitmapSize(bmp,w,h);
cols := getBitmapAreaColors(bmp, 0, 0, w-1, h-1);
freeBitmap(bmp);
CornerDetection(cols, tpa);
DebugTPA(tpa, '');
end.

Olly

2013-12-02 21:07:24

From: Flight
Thanks for your work on converting this and making a plugin, bg5. I've tried using this on the RS mini-map (via SMART) and the actual 'CornerDetection()' procedure is giving me access violation errors. Any thoughts on what might be going wrong here? Here's what I'm trying to use:


I don't think it's designed to process a full image I think it's use is more something like…


findColors(tpaWall, clWhite, minimap);
DrawTPABitmap(bmp, tpaWall, clBlack);
cols := getBitmapAreaColors(...);
CornerDetction(cols, tpaCorners);

bg5

2013-12-03 17:39:06

From: Flight
Thanks for your work on converting this and making a plugin, bg5. I've tried using this on the RS mini-map (via SMART) and the actual 'CornerDetection()' procedure is giving me access violation errors. Any thoughts on what might be going wrong here? Here's what I'm trying to use:


Yes, it's not designed for multicolor images. Background must be white and all other colors are treated like a one color(shape).
SKy Scripter said that object should be centered so I guess your error is because your shape touches edges of the image ( actually it's intepreted as one big shape without any background).

However I noticed that algorythm uses clred and clblue values in internal calculations, so passing image with that colors could potentially screw it a bit. I will change those values to -1 and -2 and generate new dll.


Here is it:
[ATTACH]22471[/ATTACH]

Olly

2013-12-20 19:17:45

I managed to get Slacky (warpie on villavu) to take a look at it.. he only spent like 15 mins on it but made a massive speed improvement!

Using the 'big bitmap' in the example above took 300ms now it's taking 32ms! :)

I guess im leaking, but I don't see anything wrong with it only massive gains.. :p

source @ https://github.com/Olly-/libTesseract/blob/master/src/libtesseractmatrix.pas#L322

bg5

2013-12-21 07:10:17

From: Olly
I managed to get Slacky (warpie on villavu) to take a look at it.. he only spent like 15 mins on it but made a massive speed improvement!

Using the 'big bitmap' in the example above took 300ms now it's taking 32ms! :)

I guess im leaking, but I don't see anything wrong with it only massive gains.. :p

source @ https://github.com/Olly-/libTesseract/blob/master/src/libtesseractmatrix.pas#L322


Nice!
Maybe he could also modify it to get exact apex of corner instead of marking whole corner?