У меня есть программа распознавания контуров в Opencv, где пользователь рисует каракули на холсте (белом), а затем находит его контуры, аппроксимирует их в многоугольник и показывает синими линиями.
Текущий пример ввода:
< р>Текущий пример вывода:
Одна из проблем, с которой я столкнулся в текущей реализации, заключается в том, что синие контурные линии начинают выглядеть странно и толсто, как только я добавляю новые каракули одну за другой. Это означает, что предыдущие каракули будут пересчитываться снова и снова каждый раз, когда я добавляю новую каракули, в результате чего синяя контурная линия становится все толще и толще, поскольку программа не распознает, что контур для этих предыдущих каракулей уже был нарисован. Поэтому только последняя нарисованная каракуля выглядит так, как хотелось:
< /p>
Вот мой код:
ScribbleWidget::ScribbleWidget(QWidget *parent)
: QLabel(parent), _drawing(false), _penWidth(20) {
_canvas = cv::Mat::zeros(600, 800, CV_8UC3);
_canvas.setTo(cv::Scalar(255, 255, 255));
updateDisplay(); // initially shows a white empty canvas
}
void ScribbleWidget::updateDisplay() {
QImage displayImage(_canvas.data, _canvas.cols, _canvas.rows, _canvas.step, QImage::Format_RGB888);
setPixmap(QPixmap::fromImage(displayImage.rgbSwapped()));
}
void ScribbleWidget::setCanvas(const cv::Mat &newCanvas) { // update the canvas
_canvas = newCanvas.clone();
updateDisplay();
}
cv::Mat ScribbleWidget::generateContours(const cv::Mat &inputImage, int threshold) {
cv::Mat gray, edges, contourImage;
// convert the input image to grayscale, to simplify the process of detecting edges and contours
cv::cvtColor(inputImage, gray, cv::COLOR_BGR2GRAY);
// apply Canny edge detection
cv::Canny(gray, edges, threshold, threshold * 2); // NOTE: treshold controls the detection sensitivity (higher value = fewer detected edges)
// Find contours (including hierarchy for holes)
std::vector contours; // Contours are stored as a list of points
std::vector hierarchy; // 4-element vector of integers used to store four integer values in a single structure representing the hierarchical relationships between contours
// !!! Values for Vec4i:
// [0] (Next contour): Index of the next contour at the same hierarchical level. If there is no next contour, it is set to -1.
// [1] (Previous contour): Index of the previous contour at the same hierarchical level. If there is no previous contour, it is set to -1.
// [2] (First child contour): Index of the first child contour (a contour nested inside the current contour). If there are no child contours, it is set to -1.
// [3] (Parent contour): Index of the parent contour (the contour that contains the current contour). If there is no parent (i.e., the contour is at the outermost level), it is set to -1.
cv::findContours(edges, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE); // NOTE: RETR_TREE finds all contours and includes information about nested contours (holes).
// clone of the original image that will be used to draw the contours as output
contourImage = inputImage.clone();
// Approximate contours using the Douglas-Peucker algorithm
for (size_t i = 0; i < contours.size(); ++i) {
std::vector approxPolygon;
double epsilon = 0.002 * cv::arcLength(contours[i], true); // Adjust epsilon for simplification
cv::approxPolyDP(contours[i], approxPolygon, epsilon, true); // approximate the contours to polygons
// Draw the approximated polygon in blue
cv::polylines(contourImage, std::vector{approxPolygon}, true, cv::Scalar(255, 0, 0), 2);
}
return contourImage; // Contains the drawn contours
}
void ScribbleWidget::mousePressEvent(QMouseEvent *event) { // drawing begins
if (event->button() == Qt::LeftButton) {
_drawing = true;
_lastPoint = event->pos(); // record the starting point
}
}
void ScribbleWidget::mouseMoveEvent(QMouseEvent *event) { // lines are drawn on the canvas upon movement
if (_drawing && (event->buttons() & Qt::LeftButton)) {
cv::Point currentPoint(event->pos().x(), event->pos().y());
// draw a line from the last point to the current point ( Scalar = line color, LINE_AA = Anti-aliasing)
cv::line(_canvas, cv::Point(_lastPoint.x(), _lastPoint.y()), currentPoint, cv::Scalar(0, 0, 0), _penWidth, cv::LINE_AA);
_lastPoint = event->pos(); // update lastPoint to the current mouse position
updateDisplay(); // refresh the image shown on the widget
}
}
void ScribbleWidget::mouseReleaseEvent(QMouseEvent *event) { // drawing stops
if (event->button() == Qt::LeftButton) {
_drawing = false;
emit drawingFinished(_canvas); // pass the current canvas to MainWindow to process further with generate contours
}
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto *widget = new QWidget(this);
auto *layout = new QVBoxLayout(widget);
setCentralWidget(widget);
_scribbleWidget = new ScribbleWidget(this); // This is the area where the user can draw
layout->addWidget(_scribbleWidget);
QObject::connect(_scribbleWidget, &ScribbleWidget::drawingFinished, this, &MainWindow::onDrawingFinished); // When mouse button has been released, the drawing is finished too
}
void MainWindow::onDrawingFinished(const cv::Mat &image) { // image is the drawing
cv::Mat contourImage = _scribbleWidget->generateContours(image, 50); // the drawn scribble image is passed so its contour is traced
_scribbleWidget->setCanvas(contourImage); // The updated canvas (containing the contours) is back on the widget
}
Есть ли способ избежать пересчета предыдущих каракулей? Я новичок в Opencv, поэтому я ценю простые инструкции и, в идеале, фрагменты кода в качестве помощи
Спасибо всем!
У меня есть программа распознавания контуров в Opencv, где пользователь рисует каракули на холсте (белом), а затем находит его контуры, аппроксимирует их в многоугольник и показывает синими линиями. Текущий пример ввода: [img]https://i.sstatic.net/V0lm5VSt.png[/img]
< р>Текущий пример вывода: [img]https://i.sstatic.net/zbOivr5n.png[/img]
Одна из проблем, с которой я столкнулся в текущей реализации, заключается в том, что синие контурные линии начинают выглядеть странно и толсто, как только я добавляю новые каракули одну за другой. Это означает, что предыдущие каракули будут пересчитываться снова и снова каждый раз, когда я добавляю новую каракули, в результате чего синяя контурная линия становится все толще и толще, поскольку программа не распознает, что контур для этих предыдущих каракулей уже был нарисован. Поэтому только последняя нарисованная каракуля выглядит так, как хотелось: [img]https://i.sstatic.net/4sUHjqLj.png[/img] < /p> Вот мой код: [code] ScribbleWidget::ScribbleWidget(QWidget *parent) : QLabel(parent), _drawing(false), _penWidth(20) { _canvas = cv::Mat::zeros(600, 800, CV_8UC3); _canvas.setTo(cv::Scalar(255, 255, 255)); updateDisplay(); // initially shows a white empty canvas }
// Find contours (including hierarchy for holes) std::vector contours; // Contours are stored as a list of points std::vector hierarchy; // 4-element vector of integers used to store four integer values in a single structure representing the hierarchical relationships between contours // !!! Values for Vec4i: // [0] (Next contour): Index of the next contour at the same hierarchical level. If there is no next contour, it is set to -1. // [1] (Previous contour): Index of the previous contour at the same hierarchical level. If there is no previous contour, it is set to -1. // [2] (First child contour): Index of the first child contour (a contour nested inside the current contour). If there are no child contours, it is set to -1. // [3] (Parent contour): Index of the parent contour (the contour that contains the current contour). If there is no parent (i.e., the contour is at the outermost level), it is set to -1.
cv::findContours(edges, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE); // NOTE: RETR_TREE finds all contours and includes information about nested contours (holes).
// clone of the original image that will be used to draw the contours as output contourImage = inputImage.clone();
// Approximate contours using the Douglas-Peucker algorithm for (size_t i = 0; i < contours.size(); ++i) { std::vector approxPolygon; double epsilon = 0.002 * cv::arcLength(contours[i], true); // Adjust epsilon for simplification cv::approxPolyDP(contours[i], approxPolygon, epsilon, true); // approximate the contours to polygons
// Draw the approximated polygon in blue cv::polylines(contourImage, std::vector{approxPolygon}, true, cv::Scalar(255, 0, 0), 2); }
return contourImage; // Contains the drawn contours }
void ScribbleWidget::mousePressEvent(QMouseEvent *event) { // drawing begins if (event->button() == Qt::LeftButton) { _drawing = true; _lastPoint = event->pos(); // record the starting point } }
void ScribbleWidget::mouseMoveEvent(QMouseEvent *event) { // lines are drawn on the canvas upon movement if (_drawing && (event->buttons() & Qt::LeftButton)) { cv::Point currentPoint(event->pos().x(), event->pos().y());
// draw a line from the last point to the current point ( Scalar = line color, LINE_AA = Anti-aliasing) cv::line(_canvas, cv::Point(_lastPoint.x(), _lastPoint.y()), currentPoint, cv::Scalar(0, 0, 0), _penWidth, cv::LINE_AA); _lastPoint = event->pos(); // update lastPoint to the current mouse position updateDisplay(); // refresh the image shown on the widget } }
void ScribbleWidget::mouseReleaseEvent(QMouseEvent *event) { // drawing stops if (event->button() == Qt::LeftButton) { _drawing = false; emit drawingFinished(_canvas); // pass the current canvas to MainWindow to process further with generate contours } }
auto *widget = new QWidget(this); auto *layout = new QVBoxLayout(widget); setCentralWidget(widget);
_scribbleWidget = new ScribbleWidget(this); // This is the area where the user can draw layout->addWidget(_scribbleWidget);
QObject::connect(_scribbleWidget, &ScribbleWidget::drawingFinished, this, &MainWindow::onDrawingFinished); // When mouse button has been released, the drawing is finished too }
void MainWindow::onDrawingFinished(const cv::Mat &image) { // image is the drawing cv::Mat contourImage = _scribbleWidget->generateContours(image, 50); // the drawn scribble image is passed so its contour is traced _scribbleWidget->setCanvas(contourImage); // The updated canvas (containing the contours) is back on the widget }
[/code] Есть ли способ избежать пересчета предыдущих каракулей? Я новичок в Opencv, поэтому я ценю простые инструкции и, в идеале, фрагменты кода в качестве помощи :) Спасибо всем!