drawing bezier paths with cairo - c++

I need to draw a collection of Bezier paths with the help of Cairo in C++.
So far I can draw a single path.
But I need to draw many paths consisting of multiple bezier curves and lines.
I load collection of paths from the external dictionaty generated by python.
Here, for this minimal example I introduce a path by the variable path0.
It is a vector, consisting of 2 subvectors. The first one represents a bezier spline, while the second one is a simple straight line.
The first subvector naturally has 4 components, while the second one has just 2.
Here is the example of my code.
#include <iostream>
#include <vector>
#include "cairo.h"
#include <string>
int main(int argc, char* argv[])
{
cairo_surface_t* surface;
cairo_t* cr;
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1000, 1000);
cr = cairo_create(surface);
cairo_scale(cr, 1, 1);
cairo_set_line_width(cr, 1);
cairo_set_source_rgb(cr, 0, 0, 0);
// Here goes the svg-path. It is a bezier curve and unconnected line.
std::vector<std::vector<std::vector<double>>> path0 = { {{82.801,-204.48},{66.241,-204.48},{52.561,-214.56},{52.561,-235.44}},{{277.921,-192.24},{277.921,-173.5}} };
// Here I put the initial point in the middle of the canvas.
double x0 = 300+path0[0][0][0];
double y0 = 700+ path0[0][0][1];
cairo_move_to(cr, x0, y0);
// Parsing path
for (int i = 0; i < path0.size(); i++) {
if (path0[i].size() == 4) {
cairo_rel_curve_to(cr, path0[i][1][0], path0[i][1][1], path0[i][2][0], path0[i][2][1], path0[i][3][0], path0[i][3][1]);
cairo_stroke(cr);
}
if (path0[i].size() == 2) {
cairo_rel_line_to(cr, symbol[0][i][1][0], symbol[0][i][1][1]);
cairo_stroke(cr);
}
}
cairo_surface_write_to_png(surface, "stroke.png");
cairo_destroy(cr);
cairo_surface_destroy(surface);
return 0;
}
The point is it I see only one curve on the canvas. Probably, the second one is outside.
Could you help me to understand what I'm doing wrong?
Yaroslav.

Could you clarify what you are expecting to see? With the following change, I get something like two lines, but I am not sure it is what you want to see.
--- t.cpp.orig 2020-10-13 17:00:12.404871474 +0200
+++ t.cpp 2020-10-13 17:04:20.573101739 +0200
## -29,13 +29,12 ## int main(int argc, char* argv[])
for (int i = 0; i < path0.size(); i++) {
if (path0[i].size() == 4) {
cairo_rel_curve_to(cr, path0[i][1][0], path0[i][1][1], path0[i][2][0], path0[i][2][1], path0[i][3][0], path0[i][3][1]);
- cairo_stroke(cr);
}
if (path0[i].size() == 2) {
- cairo_rel_line_to(cr, symbol[0][i][1][0], symbol[0][i][1][1]);
- cairo_stroke(cr);
+ cairo_rel_line_to(cr, path0[i][1][0], path0[i][1][1]);
}
}
+ cairo_stroke(cr);
cairo_surface_write_to_png(surface, "stroke.png");
cairo_destroy(cr);
cairo_surface_destroy(surface);
Result is:
What happens in the above program is:
First you call cairo_move_to(cr, 382.8, 495.5). This decides where the following curve begins.
Then there is call to cairo_rel_curve_to(). This adds a curve from the current point to something like 382.8+52.5, 495.5-235.4. The other two points describe how the curve is bent.
Next there is a call to cairo_rel_line_to(cr, 277.9, -173.5);.
In my version, this adds a line segment starting from the end of the curve.
In your version, the call to cairo_stroke() deleted the path. Since there is no current point, the line_to does not do anything (well, it sets the current point for following drawing commands).

Related

How to correctly use VTK ConstrainedDelaunay2D?

I've started from the VTK ConstrainedDelaunay2D example and added my own points:
#include <vtkSmartPointer.h>
#include <vtkDelaunay2D.h>
#include <vtkCellArray.h>
#include <vtkProperty.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkPolygon.h>
#include <vtkMath.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkNamedColors.h>
#include <vtkVersionMacros.h> // For version macros
int main(int, char *[])
{
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
int ptsHeight = 400;
std::vector<std::vector<int>> pts{ {166, 127},{103, 220},{166, 190},{174, 291},{189, 226},{227, 282},{213, 187},{242, 105},{196, 131},{182, 83} };
for (size_t i = 0; i < pts.size(); i++)
{
// !important: flip y
int x = pts[i][0];
int y = ptsHeight - pts[i][1];
points->InsertNextPoint(x, y, 0);
}
vtkSmartPointer<vtkPolyData> aPolyData = vtkSmartPointer<vtkPolyData>::New();
aPolyData->SetPoints(points);
// Create a cell array to store the polygon in
vtkSmartPointer<vtkCellArray> aCellArray = vtkSmartPointer<vtkCellArray>::New();
// Define a polygonal hole with a clockwise polygon
vtkSmartPointer<vtkPolygon> aPolygon = vtkSmartPointer<vtkPolygon>::New();
for (unsigned int i = 0; i < pts.size(); i++)
{
aPolygon->GetPointIds()->InsertNextId(i);
}
aCellArray->InsertNextCell(aPolygon);
// Create a polydata to store the boundary. The points must be the
// same as the points we will triangulate.
vtkSmartPointer<vtkPolyData> boundary =
vtkSmartPointer<vtkPolyData>::New();
boundary->SetPoints(aPolyData->GetPoints());
boundary->SetPolys(aCellArray);
// Triangulate the grid points
vtkSmartPointer<vtkDelaunay2D> delaunay =
vtkSmartPointer<vtkDelaunay2D>::New();
delaunay->SetInputData(aPolyData);
delaunay->SetSourceData(boundary);
// Visualize
vtkSmartPointer<vtkPolyDataMapper> meshMapper =
vtkSmartPointer<vtkPolyDataMapper>::New();
meshMapper->SetInputConnection(delaunay->GetOutputPort());
vtkSmartPointer<vtkNamedColors> colors =
vtkSmartPointer<vtkNamedColors>::New();
vtkSmartPointer<vtkActor> meshActor =
vtkSmartPointer<vtkActor>::New();
meshActor->SetMapper(meshMapper);
meshActor->GetProperty()->EdgeVisibilityOn();
meshActor->GetProperty()->SetEdgeColor(colors->GetColor3d("Peacock").GetData());
meshActor->GetProperty()->SetInterpolationToFlat();
meshActor->GetProperty()->SetBackfaceCulling(true);
// Create a renderer, render window, and interactor
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->AddRenderer(renderer);
vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
renderWindowInteractor->SetRenderWindow(renderWindow);
// Add the actor to the scene
renderer->AddActor(meshActor);
//renderer->AddActor(boundaryActor);
renderer->SetBackground(colors->GetColor3d("Mint").GetData());
// Render and interact
renderWindow->SetSize(640, 480);
renderWindow->Render();
renderWindowInteractor->Start();
return EXIT_SUCCESS;
}
I'm experiencing two issues:
I get different results if I flip the Y coordinates: why is that ?
Why are there faces pointing in the wrong direction (flipped normal / wrong winding )?
Here's what I mean by the 1st issue:
If I don't flip the Y coordinates I get this:
I get the same effect if I don't flip the Y axis but insert the boundary polygon in reverse order:
for (unsigned int i = 0; i < pts.size(); i++)
{
aPolygon->GetPointIds()->InsertNextId(pts.size() - 1 - i);
}
I don't think I fully understand how the boundary/constraint works.
I thoght that the same points should produce the same triangulation wether the vertices are flipped vertically or not. (I suspect the order of indices changes then ?)
Regarding the second issue (unpredictable flipped faces) I'm not sure what the best way forward is. I had a look at the vtkDelaunay2D class and couldn't find anything related.
(I've tried setting projection plane mode to VTK_DELAUNAY_XY_PLANE, but it didn't seem to affect the output)
I've also tried to use vtkPolyDataNormals but got no output:
vtkSmartPointer<vtkPolyDataNormals> normalGenerator = vtkSmartPointer<vtkPolyDataNormals>::New();
normalGenerator->SetInputData(delaunay->GetOutput());
normalGenerator->ComputePointNormalsOff();
normalGenerator->ComputeCellNormalsOn();
normalGenerator->FlipNormalsOn();
normalGenerator->Update();
(normalGenerator's output has 0 cells and points)
Is there a way to compute constrained delaunay triangulation for a list of 2d points and ensure all the faces point the same way ? (If so, how ? Would it be possible to do this with the vtkDelaunay2D class alone or is it necessary to use other filters?)
Any hints/tips are more than welcome :)
I'm using VTK 8.2 by the way.
the flipping in y effectively reverses the faces orientation (what is clockwise becomes anti-clockwise, like in a mirror).
I'm not sure I can reproduce your example above. A quick test in python seems to give the expected behavior, maybe you can start from this and map it to your c++ version:
import vedo
pts = [
[166, 127],
[103, 220],
[166, 190],
[174, 291],
[189, 226],
[227, 282],
[213, 187],
[242, 105],
[196, 131],
[182, 83],
]
ids = [[2,4,6], [0,2,8]] # faces to erase by pt-index (clockwise)
dly = vedo.delaunay2D(pts, mode='xy', boundaries=ids)
dly.c('grey5').lc('red4').lw(2)
labels = vedo.Points(pts).labels('id').z(1)
vedo.show(labels, dly, axes=1)

Why does QPainter::drawPoint draw a horizontal line segment?

I'm trying to draw a 3-pixel large point with QPainter. But the following code instead draws a horizontal line with width of 3 pixels.
#include <QPainter>
#include <QImage>
int main()
{
const int w=1000, h=1000;
QImage img(w, h, QImage::Format_RGBX8888);
{
QPainter p(&img);
p.fillRect(0,0,w,h,Qt::black);
p.scale(w,h);
p.setPen(QPen(Qt::red, 3./w, Qt::SolidLine, Qt::RoundCap));
p.drawPoint(QPointF(0.1,0.1));
}
img.save("test.png");
}
Here's the top left corner of the resulting image:
I am expecting to get a point which is red circle, or at least a square — but instead I get this line segment. If I comment out p.scale(w,h) and draw the point with width 3 (instead of 3./w) at position (100,100), then I get a small mostly symmetric 3-pixel in height and width point.
What's going on? Why do I get a line segment instead of point as expected? And how to fix it, without resorting to drawing an ellipse or to avoiding QPainter::scale?
I'm using Qt 5.10.0 on Linux x86 with g++ 5.5.0. The same happens on Qt 5.5.1.
It appears that QPaintEngineEx::drawPoints renders points as line segments of length 1/63.. See the following code from qtbase/src/gui/painting/qpaintengineex.cpp in the Qt sources:
void QPaintEngineEx::drawPoints(const QPointF *points, int pointCount)
{
QPen pen = state()->pen;
if (pen.capStyle() == Qt::FlatCap)
pen.setCapStyle(Qt::SquareCap);
if (pen.brush().isOpaque()) {
while (pointCount > 0) {
int count = qMin(pointCount, 16);
qreal pts[64];
int oset = -1;
for (int i=0; i<count; ++i) {
pts[++oset] = points[i].x();
pts[++oset] = points[i].y();
pts[++oset] = points[i].x() + 1/63.;
pts[++oset] = points[i].y();
}
QVectorPath path(pts, count * 2, qpaintengineex_line_types_16, QVectorPath::LinesHint);
stroke(path, pen);
pointCount -= 16;
points += 16;
}
} else {
for (int i=0; i<pointCount; ++i) {
qreal pts[] = { points[i].x(), points[i].y(), points[i].x() + qreal(1/63.), points[i].y() };
QVectorPath path(pts, 2, 0);
stroke(path, pen);
}
}
}
Notice the pts[++oset] = points[i].x() + 1/63.; line in the opaque brush branch. This is the second vertex of the path — shifted with respect to the desired position of the point.
This explains why the line extends to the right of the position requested and why it depends on the scale. So, it seems the code in the OP isn't wrong for an ideal QPainter implementation, but just has come across a Qt bug (be it in the implementation of the method or in its documentation).
So the conclusion: one has to work around this problem by either using different scale, or drawing ellipses, or drawing line segments with much smaller lengths than what QPainter::drawPoints does.
I've reported this as QTBUG-70409.
Though I have not been able to pin point why exactly the problem occurs, I have got relatively close to the solution. The problem lies with scaling. I did a lots of trial and error with different scaling and width of point with the below code.
const int w=500, h=500;
const int scale = 100;
float xPos = 250;
float yPos = 250;
float widthF = 5;
QImage img(w, h, QImage::Format_RGBX8888);
{
QPainter p(&img);
p.setRenderHints(QPainter::Antialiasing);
p.fillRect(0,0,w,h,Qt::black);
p.scale(scale, scale);
p.setPen(QPen(Qt::red, widthF/(scale), Qt::SolidLine, Qt::RoundCap));
p.drawPoint(QPointF(xPos/scale, yPos/scale));
}
img.save("test.png");
The above code produces the image
My observations are
1) Due to high scaling, the point (which is just 3 pixel wide) is not able to scale proportionally at lower width (width of point), if you set width as something like 30 the round shape is visible.
2) If you want to keep the width of point low then you have to decrease the scaling.
Sadly I can not explain why at high scaling it is not expanding proportionally.

OpenGL-How do I draw randomly non-overlapping circle

I've already draw few random placed circle, but I want different numbers of circles every time I run the program. So the question really is how do I call a function in random times?
I also need them to have convex and concave effects but unable to arrange them to random circles.
Finally, I need circles not to overlap, how do I do that?
Below is my partial code so far.
void circle(){
double r=50;
int dx, dy;
dx=rand()%350;
dy=rand()%250;
glLineWidth(1);
glEnable(GL_LINE_SMOOTH);
glBegin(GL_LINES);
double i=1.0;
double j=0.0;
for (double y=r; y>=-r; y=y-1) {
i=i-0.01;
j=j+0.01;
//glColor3f(i, i, i);
glColor3f(j, j, j);
glVertex2f(-sqrt(r*r-y*y)+dx, y+dy);
glVertex2f(sqrt(r*r-y*y)+dx, y+dy);
}
glEnd();
}
void display(void){
glClear(GL_COLOR_BUFFER_BIT);
srand((unsigned)time(0));
circle();
circle();
circle();
glEnd();
glFlush();
}
The simple approach
It is enough to pick a random number and iterate. This can cause the circles to overlap.
int min_circles = 10;
int max_circles = 100;
int amount = min_circles + rand()%(max_circles-min_circles+1);
for (int i = 0; i < amount; i++)
circle();
Non overlapping
This is not a trivial matter and has been an interesting research topic. You can have several approaches to this problem for example:
Naïve Generation
Store all previously added circle positions and if you try to add a new one just do a check with the previous for overlapping. If it overlaps choose another position. If it fails several times in a row then stop.
Easing
You can also consider treating all circles as a system of points connected with springs and iterate a few times so that the springs will repel the circles from each other thus conforming to the non-overlapping rule.
Poisson Disk Sampling
You can read up on implementing Poisson Disk Sampling on the internet. The algorithm generates a uniform random distribution of points on a plane so that they are at certain distance from each other. Then just use these points as centers of circles.
how do I call a function in random times?
that one way of doing it:
if (rand() % someNumber == 0)
callFunc();
I also need them to have convex and concave effects but unable to arrange them to random circles.
i didnt understand what you mean.
Finally, I need circles not to overlap, how do I do that?
first you need to creaye circles in a structure, and store them in a collection. assume you the structure like that:
struct Circle {
float x, y, raduis;
}
and a collection like that:
std::vector<Circle> circles;
then the creation may look like that:
Circle c;
do {
bool canExit = true;
c.x = rand()*350;
c.y = rand()*250;
c.radius = 50;
for (int i = 0; i < circles.size(); i++) {
float dx = (circles[i].x - c.x);
float dy = (circles[i].y - c.y);
floar radii = circles[i].radius + c.radius;
if (dx*dx + dy*dy < (radii*radii) ) {
canExit = false;
break;
}
}
while(canExit == false);
circles.push_back(c);
of cours thats only may way, hope it works for you, and good luck.

SFML Drawing Pixel Array

I found this (http://lodev.org/cgtutor/raycasting.html) tutorial on the Internet and was interested and wanted to make my own. I wanted to do it in SFML though, and I wanted to extend it, and make a 3D version, so there could be different levels the player can walk on. Thus, you would need 1 ray for every pixel, and thus each pixel would have to be drawn independently. I found this (http://www.sfml-dev.org/tutorials/2.1/graphics-vertex-array.php) tutorial, and it seemed easy enough to have the array be of individual vertices. To start, I figured the best thing to do would be to create a class that could read the pixels returned by the rays, and draw them to the screen. I used the VertexArray, but things were not working for some reason. I tried to isolate the problem, but I've had little success. I wrote a simple vertex array of just green pixels that should fill up part of the screen, and still there are problems. The pixels only show my code and the pic. of what I mean.
#include "SFML/Graphics.hpp"
int main() {
sf::RenderWindow window(sf::VideoMode(400, 240), "Test Window");
window.setFramerateLimit(30);
sf::VertexArray pointmap(sf::Points, 400 * 10);
for(register int a = 0;a < 400 * 10;a++) {
pointmap[a].position = sf::Vector2f(a % 400,a / 400);
pointmap[a].color = sf::Color::Green;
}
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
window.draw(pointmap);
//</debug>
window.display();
}
return 0;
}
I meant for this to just fill in the top 10 rows with Green, but apparently that is not what I did... I think if I can figure out what is causing this not to work, I can probably fix the main problem. Also if you think there is a better way to do this instead, you could let me know :)
Thanks!
I think you misused the vertex array. Take a look at the sf::Quads primitive in the tutorial's table : you need to define 4 points (coordinates) to draw a quad, and a pixel is just a quad of side length 1.
So what you need is to create a vertex array of size 400*10*4, and set the same position to every following four vertices.
You can also use another method provided by SFML : draw directly a texture pixel by pixel and display it. It may not be the most efficient thing to do (you'll have to compare with vertices) but it has the advantage of being rather simple.
const unsigned int W = 400;
const unsigned int H = 10; // you can change this to full window size later
sf::UInt8* pixels = new sf::UInt8[W*H*4];
sf::Texture texture;
texture.create(W, H);
sf::Sprite sprite(texture); // needed to draw the texture on screen
// ...
for(register int i = 0; i < W*H*4; i += 4) {
pixels[i] = r; // obviously, assign the values you need here to form your color
pixels[i+1] = g;
pixels[i+2] = b;
pixels[i+3] = a;
}
texture.update(pixels);
// ...
window.draw(sprite);
The sf::Texture::update function accepts an array of sf::UInt8. They represent the color of each pixel of the texture. But as the pixels need to be 32bit RGBA, 4 following sf::UInt8 are the RGBA composants of the pixel.
Replace the line:
pointmap[a].position = sf::Vector2f(a % 400,a / 400);
With:
pointmap[a].position = sf::Vector2f(a % 400,(a/400) % 400);

OpenCV grooving detection

I have pictures of a surface with many grooves. In most cases the edges of the grooving form parallel lines so Canny and Hough transformation work very good to detect the lines and to do some characterization. However, at several places the grooving is demaged and the edges aren't parallel anymore.
I am looking for an easy way to check if a certain edge is a straight line or if there are any gaps or deviations from a straight line. I am thinking of something like the R square parameter in linear interpolation, but here I need a parameter which is more location-dependent. Do you have any other thougts how to characterize the edges?
I attached a picture of the grooving after canny edge detection. Here, the edges are straight lines and the grooving is fine. Unfortunately I don't have access to pictures with damaged grooving at the moment. However, in pictures with damaged grooving, the lines would have major gaps (at least 10% of the picture's size) or wouldn't be parallel.
The core of the technique I'm sharing below uses cv::HoughLinesP() to find line segments in a grayscale image.
The application starts by loading the input image as grayscale. Then it performs a basic pre-processing operation to enhance certain characteristics of the image, aiming to improve the detection performed by cv::HoughLinesP():
#include <cv.h>
#include <highgui.h>
#include <algorithm>
// Custom sort method adapted from: http://stackoverflow.com/a/328959/176769
// This is used later by std::sort()
struct sort_by_y_coord
{
bool operator ()(cv::Vec4i const& a, cv::Vec4i const& b) const
{
if (a[1] < b[1]) return true;
if (a[1] > b[1]) return false;
return false;
}
};
int main()
{
/* Load input image as grayscale */
cv::Mat src = cv::imread("13531682.jpg", 0);
/* Pre-process the image to enhance the characteristics we are interested at */
medianBlur(src, src, 5);
int erosion_size = 2;
cv::Mat element = cv::getStructuringElement(cv::MORPH_CROSS,
cv::Size(2 * erosion_size + 1, 2 * erosion_size + 1),
cv::Point(erosion_size, erosion_size) );
cv::erode(src, src, element);
cv::dilate(src, src, element);
/* Identify all the lines in the image */
cv::Size size = src.size();
std::vector<cv::Vec4i> total_lines;
cv::HoughLinesP(src, total_lines, 1, CV_PI/180, 100, size.width / 2.f, 20);
int n_lines = total_lines.size();
std::cout << "* Total lines: "<< n_lines << std::endl;
cv::Mat disp_lines(size, CV_8UC1, cv::Scalar(0, 0, 0));
// For debugging purposes, the block below writes all the lines into disp_lines
// for (unsigned i = 0; i < n_lines; ++i)
// {
// cv::line(disp_lines,
// cv::Point(total_lines[i][0], total_lines[i][2]),
// cv::Point(total_lines[i][3], total_lines[i][4]),
// cv::Scalar(255, 0 ,0));
// }
// cv::imwrite("total_lines.png", disp_lines);
At this point, all the line segments detected can be written to a file for visualization purposes:
At this point we need to sort our vector of lines because cv::HoughLinesP() doesn't do that, and we need the vector sorted to be able to identify groups of lines, by measuring and comparing the distance between the lines:
/* Sort lines according to their Y coordinate.
The line closest to Y == 0 is at the first position of the vector.
*/
sort(total_lines.begin(), total_lines.end(), sort_by_y_coord());
/* Separate them according to their (visible) groups */
// Figure out the number of groups by distance between lines
std::vector<int> idx_of_groups; // stores the index position where a new group starts
idx_of_groups.push_back(0); // the first line indicates the start of the first group
// The loop jumps over the first line, since it was already added as a group
int y_dist = 35; // the next groups are identified by a minimum of 35 pixels of distance
for (unsigned i = 1; i < n_lines; i++)
{
if ((total_lines[i][5] - total_lines[i-1][6]) >= y_dist)
{
// current index marks the position of a new group
idx_of_groups.push_back(i);
std::cout << "* New group located at line #"<< i << std::endl;
}
}
int n_groups = idx_of_groups.size();
std::cout << "* Total groups identified: "<< n_groups << std::endl;
The last part of the code above simply stores the index positions of the vector of lines in a new vector<int> so we know which lines starts a new group.
For instance, assume that the indexes stored in the new vector are: 0 4 8 12. Remember: they define the start of each group. That means that the ending lines of the groups are: 0, 4-1, 4, 8-1, 8, 12-1, 12.
Knowing that, we write the following code:
/* Mark the beginning and end of each group */
for (unsigned i = 0; i < n_groups; i++)
{
// To do this, we discard the X coordinates of the 2 points from the line,
// so we can draw a line from X=0 to X=size.width
// beginning
cv::line(disp_lines,
cv::Point(0, total_lines[ idx_of_groups[i] ][7]),
cv::Point(size.width, total_lines[ idx_of_groups[i] ][8]),
cv::Scalar(255, 0 ,0));
// end
if (i != n_groups-1)
{
cv::line(disp_lines,
cv::Point(0, total_lines[ idx_of_groups[i+1]-1 ][9]),
cv::Point(size.width, total_lines[ idx_of_groups[i+1]-1 ][10]),
cv::Scalar(255, 0 ,0));
}
}
// mark the end position of the last group (not done by the loop above)
cv::line(disp_lines,
cv::Point(0, total_lines[n_lines-1][11]),
cv::Point(size.width, total_lines[n_lines-1][12]),
cv::Scalar(255, 0 ,0));
/* Save the output image and display it on the screen */
cv::imwrite("groups.png", disp_lines);
cv::imshow("groove", disp_lines);
cv::waitKey(0);
cv::destroyWindow("groove");
return 0;
}
And the resulting image is:
It's not a perfect match, but it's close. With a little bit of tweaks here and there this approach can get much better. I would start by writing a smarter logic for sort_by_y_coord, which should discard lines that have small distances between the X coordinates (i.e. small line segments), and also lines that are not perfectly aligned on the X axis (like the one from the second group in the output image). This suggestion makes much more sense after you take the time to evaluate the first image generated by the application.
Good luck.
What immediately comes to mind would be a Hough Transform. This is a voting scheme in line space, which takes each possible line and gives you a score for it. In the code I linked to above, you could simply set a threshold that approximates ~10% of screwed up grooves/lines.