SFML C++ Canny edge detection double edges - c++

So, I decided to create a simple Canny edge detector just as exercise before biting harder topics with image processing.
I tried to follow the typical path of Canny:
1. Grayscaling the image
2. Gaussian filter to blur the noise
3. Edge detection - I use both Sobel and Scharr
4. Edge thinning - I used non-maximum suppression in direction depending on gradient direction - vertical, horizontal, 45 diagonal or 135 diagonal
5. Hysteresis
I somehow managed to get it working with Scharr's detection but I have recurring problem with double or multiple edges, espacially with Sobel. I can't really find a set of parameters which will make it work.
My algorithm for Sobel:
void sobel(sf::Image &image, pixldata **garray, float division)
{
int t1 = 0, t2 = 0, t3 = 0, t4 = 0;
sf::Color color;
sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Cyan);
for (int i = 1;i < image.getSize().y - 1;i++)
{
for (int j = 1;j < image.getSize().x - 1;j++)
{
t1 = (- image.getPixel(j - 1, i - 1).r - 2 * image.getPixel(j - 1, i).r - image.getPixel(j - 1, i + 1).r + image.getPixel(j + 1, i - 1).r + 2 * image.getPixel(j + 1, i).r + image.getPixel(j + 1, i + 1).r) / division;
t2 = (- image.getPixel(j - 1, i).r - 2 * image.getPixel(j - 1, i + 1).r - image.getPixel(j, i + 1).r + image.getPixel(j + 1, i).r + 2 * image.getPixel(j + 1, i - 1).r + image.getPixel(j, i - 1).r) / division;
t3 = (- image.getPixel(j - 1, i + 1).r - 2 * image.getPixel(j, i + 1).r - image.getPixel(j + 1, i + 1).r + image.getPixel(j - 1, i - 1).r + 2 * image.getPixel(j, i - 1).r + image.getPixel(j + 1, i - 1).r) / division;
t4 = (- image.getPixel(j, i + 1).r - 2 * image.getPixel(j + 1, i + 1).r - image.getPixel(j + 1, i).r + image.getPixel(j - 1, i).r + 2 * image.getPixel(j - 1, i - 1).r + image.getPixel(j, i - 1).r) / division;
color.r = (abs(t1) + abs(t2) + abs(t3) + abs(t4));
color.g = (abs(t1) + abs(t2) + abs(t3) + abs(t4));
color.b = (abs(t1) + abs(t2) + abs(t3) + abs(t4));
garray[j][i].gx = t1;
garray[j][i].gy = t3;
garray[j][i].gtrue = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
garray[j][i].gsimpl = sqrt(t1*t1 + t2*t2);
t1 = abs(t1);
t2 = abs(t2);
t3 = abs(t3);
t4 = abs(t4);
if (t1 > t4 && t1 > t3 && t1 > t2)
garray[j][i].fi = 0;
else if (t2 > t4 && t2 > t3 && t2 > t1)
garray[j][i].fi = 45;
else if (t3 > t4 && t3 > t2 && t3 > t1)
garray[j][i].fi = 90;
else if (t4 > t3 && t4 > t2 && t4 > t1)
garray[j][i].fi = 135;
else
garray[j][i].fi = 0;
if (sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4) < 0)
{
color.r = 0;
color.g = 0;
color.b = 0;
}
else if (sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4) > 255)
{
color.r = 255;
color.g = 255;
color.b = 255;
}
else
{
color.r = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
color.g = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
color.b = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
}
bufor.setPixel(j, i, color);
}
}
image.copy(bufor, 0, 0);
}
Code for Scharr differs only in multiplying the pixels' values.
t1 = (-3 * image.getPixel(j - 1, i - 1).r - 10 * image.getPixel(j - 1, i).r - 3 * image.getPixel(j - 1, i + 1).r + 3 * image.getPixel(j + 1, i - 1).r + 10 * image.getPixel(j + 1, i).r + 3 * image.getPixel(j + 1, i + 1).r) / division;
t2 = (-3 * image.getPixel(j - 1, i).r - 10 * image.getPixel(j - 1, i + 1).r - 3 * image.getPixel(j, i + 1).r + 3 * image.getPixel(j + 1, i).r + 10 * image.getPixel(j + 1, i - 1).r + 3 * image.getPixel(j, i - 1).r) / division;
t3 = (-3 * image.getPixel(j - 1, i + 1).r - 10 * image.getPixel(j, i + 1).r - 3 * image.getPixel(j + 1, i + 1).r + 3 * image.getPixel(j - 1, i - 1).r + 10 * image.getPixel(j, i - 1).r + 3 * image.getPixel(j + 1, i - 1).r) / division;
t4 = (-3 * image.getPixel(j, i + 1).r - 10 * image.getPixel(j + 1, i + 1).r - 3 * image.getPixel(j + 1, i).r + 3 * image.getPixel(j - 1, i).r + 10 * image.getPixel(j - 1, i - 1).r + 3 * image.getPixel(j, i - 1).r) / division;
Thinning code:
void intelligentThin(sf::Image &image, int radius, pixldata **garray)
{
int xmax = image.getSize().x;
int ymax = image.getSize().y;
bool judgeandjury = true;
for (int i = 0;i < xmax;i++)
{
int leftBound = 0, rightBound = 0, ceilBound = 0, bottomBound = 0;
if (i < radius)
{
leftBound = 0;
rightBound = i + radius;
}
else if (i >= xmax - radius)
{
leftBound = i - radius;
rightBound = xmax - 1;
}
else
{
leftBound = i - radius;
rightBound = i + radius;
}
for (int j = 0;j < ymax;j++)
{
if (j < radius)
{
ceilBound = 0;
bottomBound = j + radius;
}
else if (j >= ymax - radius)
{
ceilBound = j - radius;
bottomBound = ymax - 1;
}
else
{
ceilBound = j - radius;
bottomBound = j + radius;
}
if (garray[i][j].fi == 0)
{
for (int t = leftBound; t <= rightBound; t++)
{
if ((image.getPixel(t, j).r >= image.getPixel(i, j).r) && (t != i))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 135)
{
for (int l = leftBound, t = ceilBound; (l <= rightBound && t <= bottomBound); l++, t++)
{
if ((image.getPixel(l, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 90)
{
for (int t = ceilBound; t <= bottomBound; t++)
{
if ((image.getPixel(i, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 45)
{
for (int l = rightBound, t = ceilBound; (l >= leftBound && t <= bottomBound); l--, t++)
{
if ((image.getPixel(l, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}
if (judgeandjury == false)
{
image.setPixel(i, j, sf::Color::Black);
}
judgeandjury = true;
}
leftBound = rightBound = 0;
}
}
Hysteresis code:
void hysteresis(sf::Image &image, int radius, int uplevel, int lowlevel)
{
int xmax = image.getSize().x;
int ymax = image.getSize().y;
bool judgeandjury = false;
sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Cyan);
for (int i = 0;i < xmax;i++)
{
int leftBound = 0, rightBound = 0, ceilBound = 0, bottomBound = 0;
if (i < radius)
{
leftBound = 0;
rightBound = i + radius;
}
else if (i >= xmax - radius)
{
leftBound = i - radius;
rightBound = xmax - 1;
}
else
{
leftBound = i - radius;
rightBound = i + radius;
}
for (int j = 0;j < ymax;j++)
{
int currentPoint = image.getPixel(i, j).r;
if (j < radius)
{
ceilBound = 0;
bottomBound = j + radius;
}
else if (j >= ymax - radius)
{
ceilBound = j - radius;
bottomBound = ymax - 1;
}
else
{
ceilBound = j - radius;
bottomBound = j + radius;
}
if (currentPoint > uplevel)
{
judgeandjury = true;
}
else if (currentPoint > lowlevel)
{
for (int t = leftBound; t <= rightBound; t++)
{
for (int l = ceilBound; l <= bottomBound; l++)
{
if (image.getPixel(t, l).r > uplevel)
{
judgeandjury = true;
}
}
}
}
else judgeandjury = false;
if (judgeandjury == true)
{
bufor.setPixel(i, j, sf::Color::White);
}
else
{
bufor.setPixel(i, j, sf::Color::Black);
}
judgeandjury = false;
currentPoint = 0;
}
leftBound = rightBound = 0;
}
image.copy(bufor, 0, 0);
}
The results are quite unsatisfactionary for Sobel:
Thinning the Sobel
Sobel after hysteresis
With Scharr the results are way better:
Thinned Scharr
Scharr after hysteresis
Set of parameters:
#define thinsize 1
#define scharrDivision 1
#define sobelDivision 1
#define hysteresisRadius 1
#define level 40
#define hysteresisUpperLevelSobel 80
#define hysteresisLowerLevelSobel 60
#define hysteresisUpperLevelScharr 200
#define hysteresisLowerLevelScharr 100
As you can see, there is a problem with Sobel, which generate double edges. Scharr also generates some noise but I think it's acceptable. Of course, it always can get better, if someone could give some advice :)
What is the cause of this behaviour? Does it result from my mistakes or poor algorithms or maybe is it just a case of parameters?
EDIT:
posting main()
sf::Image imydz;
imydz.loadFromFile("lena.jpg");
int x = imydz.getSize().x;
int y = imydz.getSize().y;
pixldata **garray = new pixldata *[x];
for (int i = 0;i < x;i++)
{
garray[i] = new pixldata[y];
}
monochrome(imydz);
gauss(imydz, radius, sigma);
//sobel(imydz, garray, sobelDivision);
scharr(imydz, garray, scharrDivision);
intelligentThin(imydz, thinsize, garray);
hysteresis(imydz, hysteresisRadius, hysteresisUpperLevel, hysteresisLowerLevel);
Second edit - repaired suppression:
sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Black);
for (int i = 1;i < xmax - 1;i++)
{
for (int j = 1;j < ymax - 1;j++)
{
if (garray[i][j].fi == 0)
{
if (((image.getPixel(i, j).r >= image.getPixel(i + 1, j).r) && (image.getPixel(i, j).r > image.getPixel(i - 1, j).r)) ||
((image.getPixel(i, j).r > image.getPixel(i + 1, j).r) && (image.getPixel(i, j).r >= image.getPixel(i - 1, j).r)))
{
judgeandjury = true;
}
else judgeandjury = false;
}
...
if (judgeandjury == false)
{
bufor.setPixel(i, j, sf::Color::Black);
}
else bufor.setPixel(i, j, image.getPixel(i, j));
judgeandjury = false;
}
}
image.copy(bufor, 0, 0);
Repaired Scharr on Lena
It seems strange
Another test image - strange results
Before binarization
Ready gears

I haven't read your whole code in detail, there is much too much code there. But obviously your non-maximum suppression code is wrong. Let's look at what it does for one pixel in the middle of the image, where the gradient is close to 0 degrees:
leftBound = i - radius;
rightBound = i + radius;
// ...
for (int t = leftBound; t <= rightBound; t++)
{
if ((image.getPixel(t, j).r >= image.getPixel(i, j).r) && (t != i))
{
judgeandjury = false; // it's not a maximum: suppress
}
}
// ...
if (judgeandjury == false)
{
image.setPixel(i, j, sf::Color::Black);
}
Here, radius is set to 1 by the calling code. Any other value would be bad, so this is OK. I would remove that as a parameter altogether. Now your loop is:
for (int t = i-1; t <= t+1; t++)
if (t != i)
This means that you hit exactly two values of t. So this should of course be replaced with simpler code that does not loop, it will be more readable.
This is what it now does:
if ( (image.getPixel(i-1, j).r >= image.getPixel(i, j).r)
|| (image.getPixel(i+1, j).r >= image.getPixel(i, j).r)) {
judgeandjury = false; // it's not a maximum: suppress
}
So you suppress the pixel if it is not strictly larger than its neighbors. Looking back at the Wikipedia article, it seems that they suggest the same. But in fact, this is not correct, you want the point to be strictly larger than one of the two neighbors, and larger or equal to the other. This prevents the situation where the gradient happens to be equally strong on two neighboring pixels. The actual maximum can fall right in the middle of two pixels, yielding two pixels on this local maximum gradient with exactly the same value. But let's ignore this case for now, it is possible but not all that likely.
Next, you suppress the maximum... in the input image! This means that, when you reach the next pixel on this line, you will compare its value to this value that was just suppressed. Of course it will be larger, even though it was smaller than the original value at that location. That is, non-maxima will look like maxima because you put a neighboring pixel to 0.
So: write the result of the algorithm to an output image:
if (judgeandjury == true)
{
output.setPixel(i, j, image.getPixel(i, j));
}
...which of course you need to allocate, but you already know that.
Your second problem is in the sobel function, where you compute the gradient magnitude. It clips (saturates) the output. By cutting values of the output above 255 to 255, you create very broad lines along the edges of a constant value. The test of the non-maximum suppression is satisfied at the two edges of this line, but not in the middle, where pixels have the same value as both its neighbors.
To solve this, either:
Use a floating-point buffer to store the gradient magnitude. Here you don’t need to worry about data ranges.
Divide the magnitude by some value such that it will never exceed 255. Now you’re quantifying the magnitude rather than clipping it. Quantizing should be fine in this case.
I strongly recommend that you follow (1). I typically use floating-point—values images for everything, and only convert to 8-bit ints for display. This simplified a lot of things!

Related

how to rotate raster 32bits properly?

I'm working on a 2D Graphics Engine, when I use the following code to rotate the images I get 'write access violation' exception for newBits if the image dimensions have even numbers. There is no problem on odd numbered dimensions.
Here is my image rotation code :
bool Graphics::Raster::rotate(float angle)
{
try {
unsigned int xOrigin{ mWidth / 2 };
unsigned int yOrigin{ mHeight / 2 };
std::array<Math::Vector2D, 4> boundingBoxVertices;
Math::Matrix2x2 rotationMatrix;
boundingBoxVertices[0].setX((float)xOrigin * -1.0f);
boundingBoxVertices[0].setY((float)yOrigin);
boundingBoxVertices[1].setX((float)(mWidth - xOrigin) * 1.0f);
boundingBoxVertices[1].setY((float)yOrigin);
boundingBoxVertices[2].setX((float)(mWidth - xOrigin) * 1.0f);
boundingBoxVertices[2].setY((float)(mHeight - yOrigin) * -1.0f);
boundingBoxVertices[3].setX((float)xOrigin * -1.0f);
boundingBoxVertices[3].setY((float)(mHeight - yOrigin) * -1.0f);
int x{ 0 }, y{ 0 }, maxX{ 0 }, minX{ 0 }, maxY{ 0 }, minY{ 0 };
rotationMatrix.setToRotation(angle);
for (size_t i = 0; i < 4; ++i) {
boundingBoxVertices[i] *= rotationMatrix;
boundingBoxVertices[i].round();
x = (int)boundingBoxVertices[i].getX();
y = (int)boundingBoxVertices[i].getY();
if (x < minX) {
minX = x;
}
if (x > maxX) {
maxX = x;
}
if (y < minY) {
minY = y;
}
if (y > maxY) {
maxY = y;
}
}
size_t newWidth = (size_t)(maxX - minX);
size_t newHeight = (size_t)(maxY - minY);
BYTE* newBits{ nullptr };
if (newBits = new BYTE[newWidth * newHeight * 4]{ 0 }) {
int newOrgX = newWidth / 2;
int newOrgY = newHeight / 2;
Math::Vector2D pixVec{ 0.0f, 0.0f };
int oldCoordX{ 0 };
int oldCoordY{ 0 };
int newCoordX{ 0 };
int newCoordY{ 0 };
unsigned int oldIndex{ 0 };
unsigned int newIndex{ 0 };
for (size_t i = 0; i < mWidth * mHeight; ++i) {
oldCoordX = i % mWidth - xOrigin;
oldCoordY = yOrigin - i / mWidth;
pixVec.setX((float)oldCoordX);
pixVec.setY((float)oldCoordY);
pixVec *= rotationMatrix;
pixVec.round();
newCoordX = (unsigned int)(pixVec.getX() + newOrgX);
newCoordY = (unsigned int)(newOrgY - pixVec.getY());
oldIndex = i * 4;
newIndex = (newCoordY * newWidth * 4) + ((newCoordX) * 4);
newBits[newIndex + 0] = m32Bits[oldIndex + 0];
newBits[newIndex + 1] = m32Bits[oldIndex + 1];
newBits[newIndex + 2] = m32Bits[oldIndex + 2];
newBits[newIndex + 3] = m32Bits[oldIndex + 3];
}
if (angle != 0.0f || angle != 90.0f || angle != 180.0f || angle != 270.0f || angle != 360.0f ||
angle != -0.0f || angle != -90.0f || angle != -180.0f || angle != -270.0f || angle != -360.0f ) {
for (size_t i = 0; i < newHeight; ++i) {
for (size_t j = 0; j < newWidth; ++j) {
if (j != 0 && j != newWidth - 1) {
if (newBits[(i * newWidth * 4) + (j * 4) + 0] == 0 &&
newBits[(i * newWidth * 4) + (j * 4) + 1] == 0 &&
newBits[(i * newWidth * 4) + (j * 4) + 2] == 0 &&
newBits[(i * newWidth * 4) + (j * 4) + 3] == 0) {
newBits[(i * newWidth * 4) + (j * 4) + 0] = (newBits[(i * newWidth * 4) + ((j - 1) * 4) + 0] +
newBits[(i * newWidth * 4) + ((j + 1) * 4) + 0]) / 2;
newBits[(i * newWidth * 4) + (j * 4) + 1] = (newBits[(i * newWidth * 4) + ((j - 1) * 4) + 1] +
newBits[(i * newWidth * 4) + ((j + 1) * 4) + 1]) / 2;
newBits[(i * newWidth * 4) + (j * 4) + 2] = (newBits[(i * newWidth * 4) + ((j - 1) * 4) + 2] +
newBits[(i * newWidth * 4) + ((j + 1) * 4) + 2]) / 2;
newBits[(i * newWidth * 4) + (j * 4) + 3] = (newBits[(i * newWidth * 4) + ((j - 1) * 4) + 3] +
newBits[(i * newWidth * 4) + ((j + 1) * 4) + 3]) / 2;
}
}
}
}
}
if (set32Bits(newBits, newWidth, newHeight)) {
delete[] newBits;
return true;
} else {
delete[] newBits;
return false;
}
} else {
throw Error::Exception(L"Resim çevirme işlemi için hafızada yer açılamadı", L"Resim Düzenleme Hatası");
}
} catch (Error::Exception& ex) {
Error::ShowError(ex.getErrorMessage(), ex.getErrorTitle());
return false;
}
}
What am I doing wrong here?
I can't use a third-party to rotate the images, I must use this function.
Thanks in advance.
The problem was a index problem when setting the newBits.
Here is the updated function :
BYTE* Graphics::RotateBits(const BYTE* bits, const int width, const int height, float angle, int* newWidth, int* newHeight)
{
try {
int xOrigin{ width / 2 };
int yOrigin{ height / 2 };
std::array<Math::Vector2D, 4> boundingBoxVertices;
Math::Matrix2x2 rotationMatrix;
boundingBoxVertices[0].setX((float)xOrigin * -1.0f);
boundingBoxVertices[0].setY((float)yOrigin);
boundingBoxVertices[1].setX((float)(width - xOrigin) * 1.0f);
boundingBoxVertices[1].setY((float)yOrigin);
boundingBoxVertices[2].setX((float)(width - xOrigin) * 1.0f);
boundingBoxVertices[2].setY((float)(height - yOrigin) * -1.0f);
boundingBoxVertices[3].setX((float)xOrigin * -1.0f);
boundingBoxVertices[3].setY((float)(height - yOrigin) * -1.0f);
int x{ 0 }, y{ 0 }, maxX{ 0 }, minX{ 0 }, maxY{ 0 }, minY{ 0 };
rotationMatrix.setToRotation(angle);
for (int i = 0; i < 4; ++i) {
boundingBoxVertices[i] *= rotationMatrix;
boundingBoxVertices[i].round();
x = (int)boundingBoxVertices[i].getX();
y = (int)boundingBoxVertices[i].getY();
if (x < minX) {
minX = x;
}
if (x > maxX) {
maxX = x;
}
if (y < minY) {
minY = y;
}
if (y > maxY) {
maxY = y;
}
}
*newWidth = (maxX - minX);
*newHeight = (maxY - minY);
BYTE* newBits = new BYTE[*newWidth * *newHeight * 4]{ 0 };
int newOrgX = *newWidth / 2;
int newOrgY = *newHeight / 2;
Math::Vector2D pixVec{ 0.0f, 0.0f };
int oldCoordX{ 0 };
int oldCoordY{ 0 };
int newCoordX{ 0 };
int newCoordY{ 0 };
int oldIndex{ 0 };
int newIndex{ 0 };
for (int i = 0; i < width * height; ++i) {
oldCoordX = i % width - xOrigin;
oldCoordY = yOrigin - i / width;
pixVec.setX((float)oldCoordX);
pixVec.setY((float)oldCoordY);
pixVec *= rotationMatrix;
pixVec.round();
newCoordX = (int)pixVec.getX() + newOrgX;
newCoordY = newOrgY - (int)pixVec.getY();
oldIndex = i * 4;
newIndex = (newCoordY * *newWidth * 4) + (newCoordX * 4);
if (newIndex >= 0 && newIndex <= *newWidth * *newHeight * 4 - 4) {
newBits[newIndex + 0] = bits[oldIndex + 0];
newBits[newIndex + 1] = bits[oldIndex + 1];
newBits[newIndex + 2] = bits[oldIndex + 2];
newBits[newIndex + 3] = bits[oldIndex + 3];
}
}
if (((int)angle) % 90) {
int index{ 0 };
int prevIndex{ 0 };
int nextIndex{ 0 };
for (int i = 0; i < *newHeight; ++i) {
for (int j = 0; j < *newWidth; ++j) {
if (j != 0 && j != *newWidth - 1) {
index = (i * *newWidth * 4) + (j * 4);
if (newBits[index + 0] == 0 &&
newBits[index + 1] == 0 &&
newBits[index + 2] == 0 &&
newBits[index + 3] == 0) {
prevIndex = (i * *newWidth * 4) + ((j - 1) * 4);
nextIndex = (i * *newWidth * 4) + ((j + 1) * 4);
newBits[index + 0] = (newBits[prevIndex + 0] + newBits[nextIndex + 0]) / 2;
newBits[index + 1] = (newBits[prevIndex + 1] + newBits[nextIndex + 1]) / 2;
newBits[index + 2] = (newBits[prevIndex + 2] + newBits[nextIndex + 2]) / 2;
newBits[index + 3] = (newBits[prevIndex + 3] + newBits[nextIndex + 3]) / 2;
}
}
}
}
}
return newBits;
} catch (Error::Exception& ex) {
Error::ShowError(ex.getErrorMessage(), ex.getErrorTitle());
return nullptr;
} catch (std::exception& ex) {
Error::ShowError((LPCWSTR)ex.what(), L"Bit Düzenleme Hatası");
return nullptr;
}
}

Detect a 2d collision in C++

I need to create a function that returns a bool that is false if there is no collision between two sprites and true if there is, I was thinking for a long time and I can not find an exact solution, the objective is to detect if there is a collision per pixel, that is if two pixels with the alpha value (from rgba) different than 0 (it is visible) coincide in the same place in the space, the function has the following signature :
bool checkPixelCollision(
const Vector2& pixelPos1,
const Vector2& pixelSize1,
const vector<uint8_t> pixel1,
const Vector2& pixelPos2,
const Vector2& pixelSize2,
const vector<uint8_t> pixel2);
Vector2 is a struct with the next form:
struct Vector2
{
float x;
float y;
};
pixelPos1 is the position of the upper left corner of the rectangle that contains sprite 1, pixelSize1 is the size (x = width; y = height) of the rectangle that contains sprite 1, pixel1 is a vector that has the rgba values ​​of each pixel of the sprite, they are stored from 4 to 4 so that i contains the amount of r of the pixel i; i + 1 the amount of g of the pixel i; i + 2 the amount of b of the pixel i; i + 3 the amount of alpha of the pixel i, so that if i + 3 is different from 0 is a visible pixel, the size of pixel1 is given by pixelSize1.x * pixelSize1.y * 4.
The other three parameters of the header are those corresponding to sprite 2. The objective would therefore be to check when there is a collision (either on the side or corner that is) and from there establish a collision rectangle between both rectangles (the coincident area), and set two indexes that travel pixel1 and pixel2 (since each one will have to start from a different position in its corresponding vector).
The problem is that I can not find an optimal and / or easy way to do it and that it works. If anyone knows any way to do it, I would appreciate it very much.
EDIT
Here is my code (it doesn't work)
#include <algorithm>
#include <stdint.h>
#include <vector>
struct Vector2
{
float x;
float y;
};
float clamp(float val, float min, float max) {
return std::max(min, std::min(max, val));
}
bool checkPixelCollision(const Vector2& pixelPos1, const Vector2& pixelSize1, const vector<uint8_t> pixel1, const Vector2& pixelPos2, const Vector2& pixelSize2, const vector<uint8_t> pixel2) {
return check(pixelPos1,pixelSize1,pixel1,pixelPos2,pixelSize2,pixel2)||check(pixelPos2,pixelSize2,pixel2,pixelPos1,pixelSize1,pixel1);
}
bool check(const Vector2& pixelsPos1, const Vector2& pixelsSize1, const vector<uint8_t> pixels1, const Vector2& pixelsPos2, const Vector2& pixelsSize2, const vector<uint8_t> pixels2){
bool res = false;
if (pixelsPos1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y <= pixelsPos2.y + pixelsSize2.y && pixelsPos1.x >= pixelsPos2.x && pixelsPos1.y >= pixelsPos2.y) {
float i = pixelsSize2.x - (pixelsSize1.y*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x));
float j = pixelsSize2.y - (pixelsSize1.y*((pixelsPos1.y - pixelsPos2.y + pixelsSize2.y) / pixelsSize1.y));
float ifin = fmin(pixelsSize1.x - pixelsSize2.x, pixelsSize1.x);
float jfin = fmin(pixelsSize1.y - pixelsSize2.y, pixelsSize1.y);
float i2 = 0;
float j2 = 0;
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = 0;
i = pixelsSize2.x - (pixelsSize1.x*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x));
j = j + 1;
j2 = j2 + 1;
}
}
}
else if (pixelsPos1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y + pixelsSize1.y >= pixelsPos2.y && pixelsPos1.x >= pixelsPos2.x && pixelsPos1.y + pixelsSize1.y <= pixelsPos2.y + pixelsSize2.y) {
float i = clamp(pixelsSize2.x - (pixelsSize1.x*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x)), 0.0f, pixelsSize2.x);
float jfin = clamp(pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y+pixelsSize1.y) / pixelsSize1.y), 0.0f, pixelsSize1.y);
float ifin = fmin(pixelsSize1.x - pixelsSize2.x, pixelsSize1.x);
float j = 0;
float i2 = 0;
float j2 = clamp(pixelsSize1.y - pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y + pixelsSize1.y) / pixelsSize1.y),0.0f, pixelsSize1.y);
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = 0;
i = clamp(pixelsSize2.x - (pixelsSize1.x*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x)),0.0f, pixelsSize2.x);
j = j + 1;
j2 = j2 + 1;
}
}
}
else if (pixelsPos1.x + pixelsSize1.x >= pixelsPos2.x && pixelsPos1.y<= pixelsPos2.y + pixelsSize2.y && pixelsPos1.x + pixelsSize1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y >= pixelsPos2.y) {
float ifin = clamp(pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float j = clamp(pixelsSize2.y - (pixelsSize1.y*((pixelsPos1.y - pixelsPos2.y + pixelsSize2.y) / pixelsSize1.y)),0.0f, pixelsSize2.y);
float jfin = fmin(pixelsSize1.y - pixelsSize2.y, pixelsSize1.y);
float i = 0;
float i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float j2 = 0;
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
i = 0;
j = j + 1;
j2 = j2 + 1;
}
}
}
else if (pixelsPos1.x + pixelsSize1.x >= pixelsPos2.x && pixelsPos1.y + pixelsSize1.y >= pixelsPos2.y && pixelsPos1.x + pixelsSize1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y + pixelsSize1.y <= pixelsPos2.y + pixelsSize2.y) {
float jfin = clamp(pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y + pixelsSize1.y) / pixelsSize1.y), 0.0f, pixelsSize1.y);
float j = 0;
float ifin = clamp(pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float i = 0;
float i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float j2 = clamp(pixelsSize1.y - pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y + pixelsSize1.y) / pixelsSize1.y), 0.0f, pixelsSize1.y);
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
i = 0;
j = j + 1;
j2 = j2 + 1;
}
}
}
return res;
}
Start by checking if the bounding rectangles of the two sprites overlap. If they don't, great; no collision is possible. If they do overlap, calculate the overlapping rectangle for each sprite and compare pixel by pixel - if pixel a or pixel b is transparent then there is no collision caused by that pixel, if both pixels are non-transparent there is a collision and you are done. If you finish checking all pixels in the overlapping area and there are no collisions you are also done.

Labelling on an 2d array C++ got the wrong result

I am newbie and having work with connected components labelling algorithm.
My purpose is that I need to find out 3 block of light points and then calculate the coordinates of the central point of each block (kind of image processing).
But after I run the for loop, I got the same coordinate for all the central points of three blocks, and don't know what was going wrong.
Could someone here please help me!
Thanks a lot!
This is my code
for (size_t i = 0; i < 128; i++)
{
for (size_t j = 0; j < 128; j++)
{
if (pInt[i * 128 + j] <= 18000) label[i][j] = 0;
if (pInt[i * 128 + j] > 18000)
{
if (label[i-1][j-1] != 0)
{
label[i][j] = label[i-1][j-1];
}
if (label[i-1][j] != 0)
{
label[i][j] = label[i-1][j];
}
if (label[i-1][j+1] != 0)
{
label[i][j] = label[i-1][j+1];
}
if (label[i][j-1] != 0)
{
label[i][j] = label[i][j-1];
}
if ((label[i - 1][j - 1] = 0) && (label[i - 1][j] = 0) && (label[i - 1][j + 1] = 0) && (label[i][j - 1] = 0))
{
l = l + 1;
label[i][j] = l;
}
}
if (label[i][j] = 1)
{
count1++;
sumx1 = sumx1 + i;
sumy1 = sumy1 + j;
}
if (label[i][j] = 2)
{
count2++;
sumx2 = sumx2 + i;
sumy2 = sumy2 + j;
}
if (label[i][j] = 3)
{
count3++;
sumx3 = sumx3 + i;
sumy3 = sumy3 + j;
}
}
}
float y1 = (float)sumx1 / count1;
float z1 = (float)sumy1 / count1;
float y2 = (float)sumx2 / count2;
float z2 = (float)sumy2 / count2;
float ya = (float)sumx3 / count3;
float za = (float)sumy3 / count3;
printf("three points:\n1(%f, %f)\n2(%f, %f)\na(%f, %f)\n", z1 - 64, 64 - y1, z2 - 64, 64 - y2, za - 64, 64 - ya);
In your if statements you need to use the == operator to compare. The single = is assignment. For example:
if (label[i][j] == 1)
There are 6 places I see where you need to make this change.

Connected Component labeling in OpenCV using recursive algorithm

I'm trying to implement connected component labeling in OpenCV using recursive algorithm. I'm not sure what I have implemented wrongly. the algorithm is like this
B is Binary Image, LB is Labelled Binary Image
procedure connected_component(B,LB)
{
LB:=negate(B);
label:=0;
findComponents(LB,label);
display(LB);
}
procedure findComponents(LB,label)
{
for L:=0 to maxRow
for P:= 0 to maxCol
if LB[L,P] == -1 then
{
label:=label+1;
search(LB,label,L,P);
}
}
procedure search(LB,label,L,P)
{
LB[L,P]:=label;;
Nset:= neighbours(L,P);
for each(L',P') in Nset
{
if(LB[L',P'] == -1) then
search(LB,label,L',P');
}
}
I have written the code in OpenCV as follows
#include<iostream>
#include<opencv2\opencv.hpp>
using namespace cv;
using namespace std;
void findComponents(Mat res, int label);
void search(Mat res, int label, int row, int col);
int main()
{
Mat src = imread("D:/My Library/test/peppers.bmp",0);
src.convertTo(src,CV_8S);
Mat th = src.clone();
threshold(src,th,128,255,CV_8S);
Mat res = th.clone();
for(int i=0;i<res.rows;i++)
for(int j=0;j<res.cols;j++)
res.at<signed char>(i,j) = 0 - th.at<signed char>(i,j);
int label = 0;
findComponents(res,label);
waitKey(0);
return 0;
}
void findComponents(Mat res, int label)
{
for (int i = 1; i < res.rows - 1; i++)
{
for (int j = 1; j < res.cols - 1; j++)
{
if (res.at<signed char>(i, j) == -255)
{
label++;
search(res, label, i, j);
}
}
}
imshow("CC Image", res);
}
void search(Mat res, int label, int row, int col)
{
res.at<signed char>(row, col) = label;
if (res.at<signed char>(row, col + 1) == -255) search(res, label, row, col + 1);
if (res.at<signed char>(row + 1, col + 1) == -255) search(res, label, row+1, col + 1);
if (res.at<signed char>(row + 1, col) == -255) search(res, label, row + 1, col);
if (res.at<signed char>(row + 1, col - 1) == -255) search(res, label, row + 1, col - 1);
else return;
}
The code is does not works. What have I made wrong in implementing the algorithm? I'm new to OpenCV.
You have a few problems in your code. The most important is that you shouldn't use CV_8S matrices. Why?
They have values limited in range [-128, 127]
checking for values equal to -255 won't work correctly
you are limited to at most 127 connected components per image
threshold won't work as expected
maybe others...
I re-implemented your code to correct for these issues:
you should use CV_32S for your labels.
you should account for borders
you can use Mat_<Tp> for easy access, instead of .at<Tp>
Below is the code. I used applyCustomColorMap to better visualize results.
#include <opencv2/opencv.hpp>
#include <algorithm>
#include <vector>
#include <stack>
using namespace cv;
void search(Mat1i& LB, int label, int r, int c)
{
LB(r, c) = label;
// 4 connected
if ((r - 1 > 0) && LB(r - 1, c) == -1) { search(LB, label, r - 1, c ); }
if ((r + 1 < LB.rows) && LB(r + 1, c) == -1) { search(LB, label, r + 1, c ); }
if ((c - 1 > 0) && LB(r, c - 1) == -1) { search(LB, label, r , c - 1); }
if ((c + 1 < LB.cols) && LB(r, c + 1) == -1) { search(LB, label, r , c + 1); }
// 8 connected
if ((r - 1 > 0) && (c - 1 > 0) && LB(r - 1, c - 1) == -1) { search(LB, label, r - 1, c - 1); }
if ((r - 1 > 0) && (c + 1 < LB.cols) && LB(r - 1, c + 1) == -1) { search(LB, label, r - 1, c + 1); }
if ((r + 1 < LB.rows) && (c - 1 > 0) && LB(r + 1, c - 1) == -1) { search(LB, label, r + 1, c - 1); }
if ((r + 1 < LB.rows) && (c + 1 < LB.cols) && LB(r + 1, c + 1) == -1) { search(LB, label, r + 1, c + 1); }
}
int findComponents(Mat1i& LB)
{
int label = 0;
for (int r = 0; r < LB.rows; ++r) {
for (int c = 0; c < LB.cols; ++c) {
if (LB(r, c) == -1) {
++label;
search(LB, label, r, c);
}
}
}
return label;
}
int connected_components(const Mat1b& B, Mat1i& LB)
{
// Foreground is > 0
// Background is 0
LB = Mat1i(B.rows, B.cols, 0);
LB.setTo(-1, B > 0);
// Foreground labels are initialized to -1
// Background labels are initialized to 0
return findComponents(LB);
}
void applyCustomColormap(const Mat1i& src, Mat3b& dst);
int main()
{
// Load grayscale image
Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);
// Binarize the image
Mat1b bin;
threshold(img, bin, 127, 255, THRESH_BINARY);
// Find labels
Mat1i labels;
int n_labels = connected_components(bin, labels);
// Show results
Mat3b out;
applyCustomColormap(labels, out);
imshow("Labels", out);
waitKey();
return 0;
}
void applyCustomColormap(const Mat1i& src, Mat3b& dst)
{
// Create JET colormap
double m;
minMaxLoc(src, nullptr, &m);
m++;
int n = ceil(m / 4);
Mat1d u(n * 3 - 1, 1, double(1.0));
for (int i = 1; i <= n; ++i) {
u(i - 1) = double(i) / n;
u((n * 3 - 1) - i) = double(i) / n;
}
std::vector<double> g(n * 3 - 1, 1);
std::vector<double> r(n * 3 - 1, 1);
std::vector<double> b(n * 3 - 1, 1);
for (int i = 0; i < g.size(); ++i)
{
g[i] = ceil(double(n) / 2) - (int(m) % 4 == 1 ? 1 : 0) + i + 1;
r[i] = g[i] + n;
b[i] = g[i] - n;
}
g.erase(std::remove_if(g.begin(), g.end(), [m](double v){ return v > m; }), g.end());
r.erase(std::remove_if(r.begin(), r.end(), [m](double v){ return v > m; }), r.end());
b.erase(std::remove_if(b.begin(), b.end(), [](double v){ return v < 1.0; }), b.end());
Mat1d cmap(m, 3, double(0.0));
for (int i = 0; i < r.size(); ++i) { cmap(int(r[i]) - 1, 0) = u(i); }
for (int i = 0; i < g.size(); ++i) { cmap(int(g[i]) - 1, 1) = u(i); }
for (int i = 0; i < b.size(); ++i) { cmap(int(b[i]) - 1, 2) = u(u.rows - b.size() + i); }
Mat3d cmap3 = cmap.reshape(3);
Mat3b colormap;
cmap3.convertTo(colormap, CV_8U, 255.0);
// Apply color mapping
dst = Mat3b(src.rows, src.cols, Vec3b(0, 0, 0));
for (int r = 0; r < src.rows; ++r)
{
for (int c = 0; c < src.cols; ++c)
{
dst(r, c) = colormap(src(r, c));
}
}
}
Please take care that a recursive implementation is not a good idea for labeling:
it's quite slow
it may fail if you go too deep with recursion, i.e. your components are very big
I suggest to use another algorithm. Here is an implementation of (almost) your algorithm in iterative form. I strongly recommend this one over yours. It can be trivially modified to output the points for each connected component as vector<vector<Point>>, just like cv::findContours would do:
int connected_components2(const Mat1b& img, Mat1i& labels)
{
Mat1b src = img > 0;
labels = Mat1i(img.rows, img.cols, 0);
int label = 0;
int w = src.cols;
int h = src.rows;
int i;
cv::Point point;
for (int y = 0; y<h; y++)
{
for (int x = 0; x<w; x++)
{
if ((src(y, x)) > 0) // Seed found
{
std::stack<int, std::vector<int>> stack2;
i = x + y*w;
stack2.push(i);
// Current component
std::vector<cv::Point> comp;
while (!stack2.empty())
{
i = stack2.top();
stack2.pop();
int x2 = i%w;
int y2 = i / w;
src(y2, x2) = 0;
point.x = x2;
point.y = y2;
comp.push_back(point);
// 4 connected
if (x2 > 0 && (src(y2, x2 - 1) != 0))
{
stack2.push(i - 1);
src(y2, x2 - 1) = 0;
}
if (y2 > 0 && (src(y2 - 1, x2) != 0))
{
stack2.push(i - w);
src(y2 - 1, x2) = 0;
}
if (y2 < h - 1 && (src(y2 + 1, x2) != 0))
{
stack2.push(i + w);
src(y2 + 1, x2) = 0;
}
if (x2 < w - 1 && (src(y2, x2 + 1) != 0))
{
stack2.push(i + 1);
src(y2, x2 + 1) = 0;
}
// 8 connected
if (x2 > 0 && y2 > 0 && (src(y2 - 1, x2 - 1) != 0))
{
stack2.push(i - w - 1);
src(y2 - 1, x2 - 1) = 0;
}
if (x2 > 0 && y2 < h - 1 && (src(y2 + 1, x2 - 1) != 0))
{
stack2.push(i + w - 1);
src(y2 + 1, x2 - 1) = 0;
}
if (x2 < w - 1 && y2>0 && (src(y2 - 1, x2 + 1) != 0))
{
stack2.push(i - w + 1);
src(y2 - 1, x2 + 1) = 0;
}
if (x2 < w - 1 && y2 < h - 1 && (src(y2 + 1, x2 + 1) != 0))
{
stack2.push(i + w + 1);
src(y2 + 1, x2 + 1) = 0;
}
}
++label;
for (int k = 0; k <comp.size(); ++k)
{
labels(comp[k]) = label;
}
}
}
}
return label;
}

Unexpected Harris Detector results

I load the vertical and horizontal gradients into the function posted here and it calculates the sums which than make up the corner response. Why do only boarder pixels get to be found, my threshold is 0 otherwise there is 0 corners on the image. For gradients I used sobel operator.
Look at the output image below.
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
if ((i - search_size / 2 < 0 || i + search_size / 2 > image1.rows - 1) || (j - search_size / 2 < 0 || j + search_size / 2 > image1.cols - 1)) {
continue;
}
double Ix2 = 0, Iy2 = 0, Ixy = 0;
double detM=0;
double traceM=0;
double R = 0;
for (int m = i-search_size /2; m < i + search_size /2 ; m++){
for (int n = j-search_size /2; n < j + search_size/2 ; n++){
gauss = exp(-(((i - m) * (i - m)) + ((j - n) * (j - n))) / gaus_del);
//Compute Ix^2 , Iy^2 and Ixy
Ix2 += gauss*(image1.at<float>(m, n)*image1.at<float>(m, n));
Iy2 += gauss*(image2.at<float>(m, n)*image2.at<float>(m, n));
Ixy += gauss*(image1.at<float>(m, n)*image2.at<float>(m, n));
}
}
detM = (Ix2*Iy2 - Ixy*Ixy);
traceM = Ix2*Ix2 + Iy2*Iy2;
R = detM / traceM;
//cout <<i+j<< endl;
// std::cout << "R :" << Iy2 << endl;
if (R > threshold)
{
circle(image, cv::Point2f(i, j), 3.5, cv::Scalar(255, 255, 0), 1, 5);
cout << "corner found" << endl;
}
}
}
EDIT : i am using uchars now and the result looks alot better
2