Related
I'm currently following Andre Lamothe's book Tricks of the Windows Game Programming Gurus and trying to write my own software renderer with Win32 GDI. However, I got stuck at the triangle rasterization part of the book where the author is using Bresenham's line algorithm. I managed to get the rasterization to work on DirectX, but not on Win32 GDI despite identical rendering code. Both renders a triangle but the DirectX one renders it correctly while the Win32 almost renders it correctly
Link to my code: https://pastebin.com/VwySmQXS
It could be worth mention that the DirectX version is written in c++, while my own is written in C. I wasn't able to get a good screenshot of the two triangles so here is a picture painted in paint of how they both look.
Left is DirectX (how I want it to look), right is Win32 GDI:
Am I missing something here? Both draw_triangle_2d are called with the same arguments.
static void draw_top_tri(struct render_buffer *buffer,
int x1, int y1,
int x2, int y2,
int x3, int y3,
float r, float g, float b, float a)
{
unsigned int color = vec4_to_uint(vec4_mul(Vec4(r, g, b, a), 255.0f));
// this function draws a triangle that has a flat top
float dx_right, // the dx/dy ratio of the right edge of line
dx_left, // the dx/dy ratio of the left edge of line
xs, xe, // the starting and ending points of the edges
height; // the height of the triangle
int temp_x, // used during sorting as temps
temp_y,
right, // used by clipping
left;
// destination address of next scanline
unsigned int *dest_addr = NULL;
// test order of x1 and x2
if(x2 < x1)
{
temp_x = x2;
x2 = x1;
x1 = temp_x;
} // end if swap
// compute delta's
height = (float)(y3 - y1);
dx_left = (x3 - x1) / height;
dx_right = (x3 - x2) / height;
// set starting points
xs = (float)x1;
xe = (float)x2 + (float)0.5;
// perform y clipping
if(y1 < MIN_CLIP_Y)
{
// compute new xs and ys
xs = xs + dx_left * (float)(-y1 + MIN_CLIP_Y);
xe = xe + dx_right * (float)(-y1 + MIN_CLIP_Y);
// reset y1
y1 = MIN_CLIP_Y;
} // end if top is off screen
if(y3 > MAX_CLIP_X)
y3 = MAX_CLIP_X;
// compute starting address in video memory
dest_addr = (unsigned int *)buffer->memory + y1 * buffer->width;
// test if x clipping is needed
if(x1 >= MIN_CLIP_X && x1 <= MAX_CLIP_X &&
x2 >= MIN_CLIP_X && x2 <= MAX_CLIP_X &&
x3 >= MIN_CLIP_X && x3 <= MAX_CLIP_X)
{
// draw the triangle
for(temp_y = y1; temp_y <= y3; temp_y++, dest_addr += buffer->width)
{
memset((unsigned int *)dest_addr + (unsigned int)xs,
color, (unsigned int)(xe - xs + 1) * 4);
// adjust starting point and ending point
xs += dx_left;
xe += dx_right;
} // end for
} // end if no x clipping needed
else
{
// clip x axis with slower version
// draw the triangle
for(temp_y = y1; temp_y <= y3; temp_y++, dest_addr += buffer->width)
{
// do x clip
left = (int)xs;
right = (int)xe;
// adjust starting point and ending point
xs += dx_left;
xe += dx_right;
// clip line
if(left < MIN_CLIP_X)
{
left = MIN_CLIP_X;
if(right < MIN_CLIP_X)
continue;
}
if(right > MAX_CLIP_X)
{
right = MAX_CLIP_X;
if(left > MAX_CLIP_X)
continue;
}
memset((unsigned int *)dest_addr + (unsigned int)left,
color, (unsigned int)(right - left + 1) * 4);
} // end for
} // end else x clipping needed
} // end Draw_Top_Tri
static void
draw_bottom_tri(struct render_buffer *buffer,
int x1, int y1,
int x2, int y2,
int x3, int y3,
float r, float g, float b, float a)
{
unsigned int color = vec4_to_uint(vec4_mul(Vec4(r, g, b, a), 255.0f));
// this function draws a triangle that has a flat bottom
float dx_right, // the dx/dy ratio of the right edge of line
dx_left, // the dx/dy ratio of the left edge of line
xs, xe, // the starting and ending points of the edges
height; // the height of the triangle
int temp_x, // used during sorting as temps
temp_y,
right, // used by clipping
left;
// destination address of next scanline
unsigned int *dest_addr;
// test order of x1 and x2
if(x3 < x2)
{
temp_x = x2;
x2 = x3;
x3 = temp_x;
} // end if swap
// compute delta's
height = (float)(y3 - y1);
dx_left = (x2 - x1) / height;
dx_right = (x3 - x1) / height;
// set starting points
xs = (float)x1;
xe = (float)x1; // +(float)0.5;
// perform y clipping
if(y1 < MIN_CLIP_Y)
{
// compute new xs and ys
xs = xs + dx_left * (float)(-y1 + MIN_CLIP_Y);
xe = xe + dx_right * (float)(-y1 + MIN_CLIP_Y);
// reset y1
y1 = MIN_CLIP_Y;
} // end if top is off screen
if(y3 > MAX_CLIP_X)
y3 = MAX_CLIP_X;
// compute starting address in video memory
dest_addr = (unsigned int *)buffer->memory + y1 * buffer->width;
// test if x clipping is needed
if(x1 >= MIN_CLIP_X && x1 <= MAX_CLIP_X &&
x2 >= MIN_CLIP_X && x2 <= MAX_CLIP_X &&
x3 >= MIN_CLIP_X && x3 <= MAX_CLIP_X)
{
// draw the triangle
for(temp_y = y1; temp_y <= y3; temp_y++, dest_addr += buffer->width)
{
memset((unsigned int *)dest_addr + (unsigned int)xs,
color, (unsigned int)(xe - xs + 1) * 4);
// adjust starting point and ending point
xs += dx_left;
xe += dx_right;
} // end for
} // end if no x clipping needed
else
{
// clip x axis with slower version
// draw the triangle
for(temp_y = y1; temp_y <= y3; temp_y++, dest_addr += buffer->width)
{
// do x clip
left = (int)xs;
right = (int)xe;
// adjust starting point and ending point
xs += dx_left;
xe += dx_right;
// clip line
if(left < MIN_CLIP_X)
{
left = MIN_CLIP_X;
if(right < MIN_CLIP_X)
continue;
}
if(right > MAX_CLIP_X)
{
right = MAX_CLIP_X;
if(left > MAX_CLIP_X)
continue;
}
memset((unsigned int *)dest_addr + (unsigned int)left,
color, (unsigned int)(right - left + 1) * 4);
} // end for
} // end else x clipping needed
} // end Draw_Bottom_Tri}
ENGINE_CORE_EXPORT void
draw_triangle_2d(struct render_buffer *buffer,
int x1, int y1,
int x2, int y2,
int x3, int y3,
float r, float g, float b, float a)
{
// this function draws a triangle on the destination buffer
// it decomposes all triangles into a pair of flat top, flat bottom
int temp_x, // used for sorting
temp_y,
new_x;
// test for h lines and v lines
if((x1 == x2 && x2 == x3) || (y1 == y2 && y2 == y3))
return;
// sort p1,p2,p3 in ascending y order
if(y2 < y1)
{
temp_x = x2;
temp_y = y2;
x2 = x1;
y2 = y1;
x1 = temp_x;
y1 = temp_y;
} // end if
// now we know that p1 and p2 are in order
if(y3 < y1)
{
temp_x = x3;
temp_y = y3;
x3 = x1;
y3 = y1;
x1 = temp_x;
y1 = temp_y;
} // end if
// finally test y3 against y2
if(y3 < y2)
{
temp_x = x3;
temp_y = y3;
x3 = x2;
y3 = y2;
x2 = temp_x;
y2 = temp_y;
} // end if
// do trivial rejection tests for clipping
if(y3 < MIN_CLIP_Y || y1 > MAX_CLIP_X ||
(x1 < MIN_CLIP_X && x2 < MIN_CLIP_X && x3 < MIN_CLIP_X) ||
(x1 > MAX_CLIP_X && x2 > MAX_CLIP_X && x3 > MAX_CLIP_X))
return;
// test if top of triangle is flat
if(y1 == y2)
{
draw_top_tri(buffer, x1, y1, x2, y2, x3, y3, r, g, b, a);
} // end if
else
if(y2 == y3)
{
draw_bottom_tri(buffer, x1, y1, x2, y2, x3, y3, r, g, b, a);
} // end if bottom is flat
else
{
// general triangle that's needs to be broken up along long edge
new_x = x1 + (int)(0.5 + (float)(y2 - y1) * (float)(x3 - x1) / (float)(y3 - y1));
// draw each sub-triangle
draw_bottom_tri(buffer, x1, y1, new_x, y2, x2, y2, r, g, b, a);
draw_top_tri(buffer, x2, y2, new_x, y2, x3, y3, r, g, b, a);
} // end else
} // end Draw_Triangle_2D
Apologies for the wall of code below... but I'm not 100% sure of where the exact issue is. I'm using the Sutherland Hodgman clipping algorithm to find the contact points in my collision detection system (physics engine for my undergrad thesis). The vertices it returns are MOSTLY correct however I often get a lot of false positives on the InsideEdge function or incorrect intersections. If anyone can help I'd be so grateful! I have tried with many different input vertices and the result varies, however the X-axis does seem to be the most consistently correct.
Example IO with visual:
Input: clipping vertices [ (2.0, 2.0), (2.0, -2.0), (-2.0, -2.0), (-2.0, 2.0) ], poly vertices [ (2.5, 3.0), (2.5, 1.0), (0.5, 1.0), (0.5, 3.0) ]
Output: (2.0, 1.0), (0.5, 1.0), (0.5, 3.0 (wrong))
bool SAT::InsideEdge(double px, double py, double edgeMaxX, double edgeMaxY, double edgeMinX, double edgeMinY)
{
double one = (edgeMaxX - edgeMinX) * (py - edgeMinY);
double two = (edgeMaxY - edgeMinY) * (px - edgeMinX);
return (one - two) < 0;
}
void SAT::SutherlandHodgman(std::vector<Vector3>& _clipped, const Vector3& normal, const Vector3* polyVertices, const Vector3* clippingVertices)
{
const unsigned maxPoints = 16;
Vector3 newPoints[maxPoints];
for (unsigned i = 0; i < numVertices; i++)
{
newPoints[i] = polyVertices[i];
}
unsigned newSize = 0;
for (unsigned edge = 0; edge < numEdges; edge++) //for each clipping edge
{
//NOTE: axes is a 2D array to define which two axes to consider out of x, y, z
Vector3 edgeMin = clippingVertices[edge];
Vector3 edgeMax = clippingVertices[(edge + 1) % numEdges];
for (unsigned v = 0; v < numVertices; v++) //for each input vertex
{
Vector3 v1 = newPoints[v];
Vector3 v2 = newPoints[(v + 1) % numVertices];
bool insideEdge1 = InsideEdge(v1[axes[0]], v1[axes[1]], edgeMax[axes[0]], edgeMax[axes[1]], edgeMin[axes[0]], edgeMin[axes[1]]);
bool insideEdge2 = InsideEdge(v2[axes[0]], v2[axes[1]], edgeMax[axes[0]], edgeMax[axes[1]], edgeMin[axes[0]], edgeMin[axes[1]]);
if (insideEdge1 && insideEdge2)
{
newPoints[newSize] = v2;
newSize++;
}
else if (!insideEdge1 && insideEdge2)
{
newPoints[newSize] = CalculateIntersection(v1, v2, axes, edgeMin, edgeMax);
newSize++;
newPoints[newSize] = v2;
newSize++;
}
else if (insideEdge1 && !insideEdge2)
{
newPoints[newSize] = CalculateIntersection(v1, v2, axes, edgeMin, edgeMax);
newSize++;
}
}
numVertices = newSize;
if (numVertices >= maxPoints)
break;
}
for (unsigned i = 0; i < numVertices; i++)
{
//Removes duplicates before adding to the final list
VerifyVertex(_clipped, newPoints[i]);
}
}
And the calculate intersection code...
//x intersect
double num = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
double den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
double X = num / den;
//y intersect
num = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
double Y = num / den;
Vector3 intersection;
intersection[axes[0]] = X;
intersection[axes[1]] = Y;
//Get the value of the remaining axis
double percAcrossLine = 1;
if (x2 != 0)
percAcrossLine = (x1 + X) / x2;
else if (y2 != 0)
percAcrossLine = (y1 + Y) / y2;
unsigned i = 0;
if (axes[0] == 0)
{
if (axes[1] == 1)
i = 2;
else if (axes[1] == 2)
i = 1;
}
intersection[i] = v1[i] + (v2[i] - v1[i]) * percAcrossLine;
return intersection;
I have the following working Bresenham's algorithm,which finds intermediate points between two vertices.But this is for a pixel of size 1.
void Bresenham(int x1,
int y1,
int const x2,
int const y2)
{
int delta_x(x2 - x1);
// if x1 == x2, then it does not matter what we set here
signed char const ix((delta_x > 0) - (delta_x < 0));
delta_x = std::abs(delta_x) << 1;
int delta_y(y2 - y1);
// if y1 == y2, then it does not matter what we set here
signed char const iy((delta_y > 0) - (delta_y < 0));
delta_y = std::abs(delta_y) << 1;
cout << "(" << x1 << "," << y1 << ")\n";
//plot(x1, y1);
if (delta_x >= delta_y)
{
// error may go below zero
int error(delta_y - (delta_x >> 1));
while (x1 != x2)
{
// reduce error, while taking into account the corner case of error == 0
if ((error > 0) || (!error && (ix > 0)))
{
error -= delta_x;
y1 += iy;
}
// else do nothing
error += delta_y;
x1 += ix;
cout << "(" << x1 << "," << y1 << ")\n";
//plot(x1, y1);
}
}
else
{
// error may go below zero
int error(delta_x - (delta_y >> 1));
while (y1 != y2)
{
// reduce error, while taking into account the corner case of error == 0
if ((error > 0) || (!error && (iy > 0)))
{
error -= delta_y;
x1 += ix;
}
// else do nothing
error += delta_x;
y1 += iy;
cout << "(" << x1 << "," << y1 << ")\n";
//plot(x1, y1);
}
}
}
I want to control the number of intermediate points formed between the two given vertices.In the code above,I am not able to control it currently.
For e.g: If I have vertices (0,0) and (3,3); if I want 2 points in between,it would be (1,1) and (2,2).
Could you please suggest the changes to be done in my code so that I can control the number of intermediate points between given two vertices and also ,please let me know the way to determine the length of each pixel(as currently,the pixel size is fixed to be 1)
I want my overall function to look like : void Bresenham(int x1,
int y1,
int const x2,
int const y2, int TotalIntermediatePoints)
Thanks in Advance!
Seems you don't need Bresenham algo at all.
And intermediate points might have non-integer coordinates.
Just use linear interpolation
x[i] = x0 + (x1-x0) * i / (N + 1)
First question, I have tried to calculate the expression, di+1=di+2*Δy−2*Δx(yi+1−yi) for the four quadrants. Irrespective of the quadrant, the expression was found to be the same, including signs.
Am I right, or, there has been some mistakes in my calculations (hence, I am wrong)?
Second question, if this expression is only applicable for the first octet, how can I apply this to other octets? To me, there is no way to determine which octet I am working on. Coz, the value of m always represent two opposite octets. For example, if 0<m<1, it represents 1st and 5th octet. Right?
Thirdly, how can we determine the initial/starting value of di?
#include <iostream>
#include "utils.h"
void BresenhamLine(double x1, double y1, double x2, double y2, int color)
{
if(x1>x2 || y1>y2)
{
Swap(x1, x2);
Swap(y1, y2);
}
double x = x1;
double y = y1;
double dx = x2 - x1;
double dy = y2 - y1;
double dt = 2 * (dy - dx);
double ds = 2 * dy;
double d = 2*dy - dx;
PlotPixel(x, y, color);
if(dx>=dy)
{
while(x<=x2)
{
x++;
if(d<0)
{
d = d + ds;
}
else
{
y++;
d = d + dt;
}
PlotPixel(x, y, color);
}
}
else
{
while(y<=y2)
{
y++;
if(d<0)
{
x++;
d = d + dt;
}
else
{
d = d + ds;
}
PlotPixel(x, y, color);
}
}
}
int main()
{
int gm = DETECT;
int gd = DETECT;
initgraph(&gm, &gd, "");
double x1 = 0;
double y1 = 0;
double r = 50;
double x2 = 0;
double y2 = 0;
double signx = 0;
double signy = 0;
for(int theta=0 ; theta<=360 ; theta++)
{
x2 = r * cos(DegreeToRad((double) theta));
y2 = r * sin(DegreeToRad((double) theta));
x1 = 5 * cos(DegreeToRad((double) theta));
y1 = 5 * sin(DegreeToRad((double) theta));
BresenhamLine(x1, y1, x2, y2, YELLOW);
}
getch();
closegraph();
return 0;
}
The lines that go through 2nd and 4th quadrant are not showing up.
How to fix that with some minor changes in my code?
With this input: x1: 100 y1: -100 x2: -100 y2: 100
this logic:
if(x1>x2 || y1>y2)
{
Swap(x1, x2);
Swap(y1, y2);
}
fails.
This page is a good place to start. It shows code as well for 1 of the octants:
http://www.cs.helsinki.fi/group/goa/mallinnus/lines/bresenh.html
I think you need to swap the x if x1 > x2 and swap the y if y1 > y2 but not swap both if only 1 of those is true.
The external links section of the Wikipedia page contains several links to ready-made implementations that you can study.
Try this:
void BresenhamLine( double x1, double y1, double x2, double y2, int color )
{
const bool steep = (std::abs(y2 - y1) > std::abs(x2 - x1));
if(steep)
{
std::swap(x1, y1);
std::swap(x2, y2);
}
if(x1 > x2)
{
std::swap(x1, x2);
std::swap(y1, y2);
}
double dx = x2 - x1;
double dy = std::abs(y2 - y1);
double error = dx / 2;
int ystep = (y1 < y2) ? 1 : -1;
int y = (int)y1;
int maxX = (int)x2;
for(int x=(int)x1; x<maxX; x++)
{
if(steep)
{
PlotPixel(y, x, color);
}
else
{
PlotPixel(x, y, color);
}
error -= dy;
if(error < 0)
{
y += ystep;
error += dx;
}
}
}
I got this by slightly modifying the code here:http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#C.2B.2B
I'm a program that find contours in a stream, for example :
I want to find "set of points " that can describe this contours say like the red line :
the yellow part is the the moments of the contours ,
I tried to use fitLine function of opencv but the result was nonsense, any Idea how can I get the middle line of a contours, that should represent the key aspect of my Contours . by the way **I'm not asking for codes ! ** just a hint how can I do that ?
thanks in advance for any help
maybe try this approach with a distance transform and ridge detection:
cv::Mat input = cv::imread("fitLine.jpg");
cv::Mat gray;
cv::cvtColor(input,gray,CV_BGR2GRAY);
cv::Mat mask = gray>100;
cv::imshow("mask",mask);
cv::Mat dt;
cv::distanceTransform(mask,dt,CV_DIST_L1,CV_DIST_MASK_PRECISE);
cv::imshow("dt", dt/15.0f);
cv::imwrite("fitLineOut.png",255*dt/15.0f);
//care: this part doesn't work for diagonal lines, a ridge detection would be better!!
cv::Mat lines = cv::Mat::zeros(input.rows, input.cols, CV_8UC1);
//only take the maxDist of each row
for(unsigned int y=0; y<dt.rows; ++y)
{
float biggestDist = 0;
cv::Point2i biggestDistLoc(0,0);
for(unsigned int x=0; x<dt.cols; ++x)
{
cv::Point2i current(x,y);
if(dt.at<float>(current) > biggestDist)
{
biggestDist = dt.at<float>(current) ;
biggestDistLoc = current;
}
}
lines.at<unsigned char>(biggestDistLoc) = 255;
}
//and the maxDist of each row
for(unsigned int x=0; x<dt.cols; ++x)
{
float biggestDist = 0;
cv::Point2i biggestDistLoc(0,0);
for(unsigned int y=0; y<dt.rows; ++y)
{
cv::Point2i current(x,y);
if(dt.at<float>(current) > biggestDist)
{
biggestDist = dt.at<float>(current) ;
biggestDistLoc = current;
}
}
lines.at<unsigned char>(biggestDistLoc) = 255;
}
cv::imshow("max", lines);
cv::waitKey(-1);
the idea is to compute the distance transform and find the ridges within the contour.
this is how the distance transformed image looks like:
you can see that there is a local ridge maximum in the middle of the lines.
then I use a very simple method: just find the maximum distance in each row/column. That is very sloppy and should be changed for a real ridge detection or thinning method!!!!
edit: additional description:
The idea is to find all points that are in the "middle" of the contour. In mathematics/graphics, the medial axis in some kind of "middle" of an object and it's definition is to be all points that have the same minimum distance to at least two contour points at the same time.
A way to approximate the medial axis is to compute the distance transform. The distance transform is a matrix that holds for each pixel the distance to the next object point (for example a contour of an object)(see http://en.wikipedia.org/wiki/Distance_transform too). That's the first image. There you can see that the points in the middle of the lines are a little bit brighter than the points closer to the border, which means that the brightest points along the lines can be interpreted as the medial axis (approximation), since if you move away from it (orthogonal to the line direction) the distances become smaller, so the peak is the point where the distance to both borders in close to equality.
That way if you can find those "ridges" in the distance transform, you're done.
Ridge detection is normally done by a Harris Operator (see http://en.wikipedia.org/wiki/Ridge_detection ).
In the fast and dirty version that I've posted, I try to detect the ridge by accepting only the maximum value in each line and in each row. That's ok for most horizontal and vertical ridges, but will fail for diagonal ones. So maybe you really want to exchange the for loops with a real ridge detection.
Interesting task :) Here is my solution:
Here is code:
#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdarg.h>
#include <set>
#include "opencv2/opencv.hpp"
#include "fstream"
#include "iostream"
using namespace std;
using namespace cv;
int Thinning(unsigned char * ucBinedImg, unsigned char * ucThinnedImage, long lWidth, long lHeight, long lIterativeLimit)
{
if(ucBinedImg == NULL)
return -1;
if(ucThinnedImage == NULL)
return -2;
if(lIterativeLimit == -1)
lIterativeLimit = 60000;
unsigned char x1, x2, x3, x4, x5, x6, x7, x8, xp;
unsigned char g1, g2, g3, g4;
unsigned char b1, b2, b3, b4;
unsigned char np1, np2, npm;
unsigned char *pUp, *pDown, *pImg;
long lDeletedPoints = 0;
// set border
memcpy(ucThinnedImage, ucBinedImg, lWidth*lHeight);
for(long it=0; it<lIterativeLimit; it++)
{
lDeletedPoints = 0;
for(long i=1; i<lHeight-1; i++)
{
// init neighborhood
pUp = ucBinedImg + (i-1)*lWidth;
pImg = ucBinedImg + i*lWidth ;
pDown = ucBinedImg + (i+1)*lWidth ;
for( long j=1; j<lWidth-1; j++)
{
pUp++;
pImg++;
pDown++;
if(!*pImg)
continue;
x6 = *(pUp-1);
x5 = *(pImg-1);
x4 = *(pDown-1);
x7 = *pUp;
xp = *pImg;
x3 = *pDown;
x8 = *(pUp+1);
x1 = *(pImg + 1);
x2 = *(pDown + 1);
b1 = !x1 && (x2 == 1 || x3 == 1);
b2 = !x3 && (x4 == 1 || x5 == 1);
b3 = !x5 && (x6 == 1 || x7 == 1);
b4 = !x7 && (x8 == 1 || x1 == 1);
g1 = (b1 + b2 + b3 + b4) == 1;
np1 = x1|| x2;
np1 += x3 || x4;
np1 += x5 || x6;
np1 += x7 || x8;
np2 = x2|| x3;
np2 += x4 || x5;
np2 += x6 || x7;
np2 += x8 || x1;
npm = np1>np2?np2:np1;
g2 = npm>=2 && npm<=3;
g3 = (x1 && (x2 || x3 || !x8)) == 0;
g4 = (x5 && (x6 || x7 || !x4)) == 0;
// first part
if(g1 && g2 && g3)
{
// delete this point
ucThinnedImage[lWidth*i + j] = 0;
++lDeletedPoints;
}
}
}
//syn
memcpy(ucBinedImg, ucThinnedImage, lWidth*lHeight);
for(long i=1; i<lHeight-1; i++)
{
// init neighborhood
pUp = ucBinedImg + (i-1)*lWidth;
pImg = ucBinedImg + i*lWidth ;
pDown = ucBinedImg + (i+1)*lWidth ;
for( long j=1; j<lWidth-1; j++)
{
pUp++;
pImg++;
pDown++;
if(!*pImg)
continue;
x6 = *(pUp-1);
x5 = *(pImg-1);
x4 = *(pDown-1);
x7 = *pUp;
xp = *pImg;
x3 = *pDown;
x8 = *(pUp+1);
x1 = *(pImg + 1);
x2 = *(pDown + 1);
b1 = !x1 && (x2 == 1 || x3 == 1);
b2 = !x3 && (x4 == 1 || x5 == 1);
b3 = !x5 && (x6 == 1 || x7 == 1);
b4 = !x7 && (x8 == 1 || x1 == 1);
g1 = (b1 + b2 + b3 + b4) == 1;
np1 = x1|| x2;
np1 += x3 || x4;
np1 += x5 || x6;
np1 += x7 || x8;
np2 = x2|| x3;
np2 += x4 || x5;
np2 += x6 || x7;
np2 += x8 || x1;
npm = np1>np2?np2:np1;
g2 = npm>=2 && npm<=3;
g3 = (x1 && (x2 || x3 || !x8)) == 0;
g4 = (x5 && (x6 || x7 || !x4)) == 0;
// second part
if(g1 && g2 && g4)
{
// delete this point
ucThinnedImage[lWidth*i + j] = 0;
++lDeletedPoints;
}
}
}
//syn
memcpy(ucBinedImg, ucThinnedImage, lWidth*lHeight);
// if no points to be deleted
if(lDeletedPoints == 0)
break;
}
// clear edge bar
for(long i=0; i<lHeight; i++)
{
for(long j=0; j<lWidth; j++)
{
if(i<16)
ucThinnedImage[i*lWidth+j] = 0;
else if(i>=lHeight-16)
ucThinnedImage[i*lWidth+j] = 0;
else if(j<16)
ucThinnedImage[i*lWidth+j] = 0;
else if(j>=lWidth-16)
ucThinnedImage[i*lWidth+j] = 0;
}
}
return 0;
}
void Thinning(Mat& src,Mat& dst,long IterativeLimit=-1)
{
Mat bin_img=src&1;
if(!dst.empty()){dst.release();}
dst=Mat::zeros(src.size(),CV_8UC1);
Thinning(bin_img.data,dst.data,bin_img.cols,bin_img.rows,IterativeLimit);
dst*=255;
}
int main(int argc, char* argv[])
{
namedWindow("source");
namedWindow("result");
Mat img=imread("raw_image.jpg",0);
cv::threshold(img,img,128,255,cv::THRESH_BINARY);
int erosion_size=5;
Mat element = getStructuringElement( cv::MORPH_ELLIPSE,Size( 2*erosion_size + 1, 2*erosion_size+1 ),Point( erosion_size, erosion_size ) );
cv::dilate(img,img,element);
Mat thinned;
Thinning(img,thinned);
vector<Vec2f> lines;
HoughLines(thinned, lines, 0.5, CV_PI/360, 50, 0, 0 );
float hist_theta[2]={0,0};
float hist_rho[2]={0,0};
float n[2]={0,0};
for( size_t i = 0; i < lines.size(); i++ )
{
float rho = lines[i][0], theta = lines[i][1];
if(fabs(theta-CV_PI/2)<CV_PI/4)
{
hist_theta[0]+=theta;
hist_rho[0]+=rho;
n[0]+=1;
}else
{
hist_theta[1]+=theta;
hist_rho[1]+=rho;
n[1]+=1;
}
}
for( size_t i = 0; i < 2; i++ )
{
float rho = hist_rho[i]/n[i], theta = hist_theta[i]/n[i];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
pt1.x = cvRound(x0 + 1000*(-b));
pt1.y = cvRound(y0 + 1000*(a));
pt2.x = cvRound(x0 - 1000*(-b));
pt2.y = cvRound(y0 - 1000*(a));
line( thinned, pt1, pt2, Scalar(255,255,255), 1, CV_AA);
}
imshow("source",img);
imshow("result",thinned);
cv::waitKey(0);
}
Here is a hack in this source it uses two 1D histograms for postprocessing. In real life application it should use 2D histogram for close lines averaging.