Как найти лазерный кросс-центр с открытым cv

Из камеры с открытым cv я могу получить красный крест (см. рисунок ниже), я не знаю лучшего метода расчета кросс-координаты центра (x, y)? Можно предположить, что лазер красный.

enter image description here

Возможно, мне придется использовать какое-то распознавание объектов. Но мне нужно рассчитать его центр, и производительность важна.

Кто-нибудь может помочь?

Я основал как найти лазерную указку (координаты красных точек), выполнив поиск самого красного пикселя на картинке, но в этом случае центр не всегда самый красный (целая линия красная, а иногда cv вычисляет, что она более красная, чем центр).

Ответ 1

Вот как я это сделал, используя функцию goodFeaturesToTrack:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;


int main(int argc, char* argv[])
{
    Mat laserCross = imread("laser_cross.png");

    vector<Mat> laserChannels;
    split(laserCross, laserChannels);

    vector<Point2f> corners;
    // only using the red channel since it contains the interesting bits...
    goodFeaturesToTrack(laserChannels[2], corners, 1, 0.01, 10, Mat(), 3, false, 0.04);

    circle(laserCross, corners[0], 3, Scalar(0, 255, 0), -1, 8, 0);

    imshow("laser red", laserChannels[2]);
    imshow("corner", laserCross);
    waitKey();

    return 0;
}

В результате получается следующий результат:
enter image description here

Вы также можете посмотреть cornerSubPix, чтобы повысить точность ответа.

РЕДАКТИРОВАТЬ: Мне было любопытно, как выполнить василирующий ответ, поэтому я сел и попробовал. Это выглядит неплохо! Вот моя реализация того, что он описал. Для сегментации я решил использовать метод Otsu для автоматического выбора порога. Это будет работать хорошо, если у вас есть высокое разделение между лазером и фоном, иначе вы можете переключиться на детектор кромок, например Canny. Мне приходилось иметь дело с некоторыми неопределенностями угла для вертикальных линий (т.е. 0 и 180 градусов), но код, похоже, работает (может быть лучший способ справиться с неопределенностями угла).

В любом случае, вот код:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

Point2f computeIntersect(Vec2f line1, Vec2f line2);
vector<Point2f> lineToPointPair(Vec2f line);
bool acceptLinePair(Vec2f line1, Vec2f line2, float minTheta);

int main(int argc, char* argv[])
{
    Mat laserCross = imread("laser_cross.png");

    vector<Mat> laserChannels;
    split(laserCross, laserChannels);

    namedWindow("otsu", CV_WINDOW_NORMAL);
    namedWindow("intersect", CV_WINDOW_NORMAL);

    Mat otsu;
    threshold(laserChannels[2], otsu, 0.0, 255.0, THRESH_OTSU);
    imshow("otsu", otsu);

    vector<Vec2f> lines;
    HoughLines( otsu, lines, 1, CV_PI/180, 70, 0, 0 );

    // compute the intersection from the lines detected...
    int lineCount = 0;
    Point2f intersect(0, 0);
    for( size_t i = 0; i < lines.size(); i++ )
    {
        for(size_t j = 0; j < lines.size(); j++)
        {
            Vec2f line1 = lines[i];
            Vec2f line2 = lines[j];
            if(acceptLinePair(line1, line2, CV_PI / 4))
            {
                intersect += computeIntersect(line1, line2);
                lineCount++;
            }
        }

    }

    if(lineCount > 0)
    {
        intersect.x /= (float)lineCount; intersect.y /= (float)lineCount;
        Mat laserIntersect = laserCross.clone();
        circle(laserIntersect, intersect, 1, Scalar(0, 255, 0), 3);
        imshow("intersect", laserIntersect);
    }

    waitKey();

    return 0;
}

bool acceptLinePair(Vec2f line1, Vec2f line2, float minTheta)
{
    float theta1 = line1[1], theta2 = line2[1];

    if(theta1 < minTheta)
    {
        theta1 += CV_PI; // dealing with 0 and 180 ambiguities...
    }

    if(theta2 < minTheta)
    {
        theta2 += CV_PI; // dealing with 0 and 180 ambiguities...
    }

    return abs(theta1 - theta2) > minTheta;
}

// the long nasty wikipedia line-intersection equation...bleh...
Point2f computeIntersect(Vec2f line1, Vec2f line2)
{
    vector<Point2f> p1 = lineToPointPair(line1);
    vector<Point2f> p2 = lineToPointPair(line2);

    float denom = (p1[0].x - p1[1].x)*(p2[0].y - p2[1].y) - (p1[0].y - p1[1].y)*(p2[0].x - p2[1].x);
    Point2f intersect(((p1[0].x*p1[1].y - p1[0].y*p1[1].x)*(p2[0].x - p2[1].x) -
                       (p1[0].x - p1[1].x)*(p2[0].x*p2[1].y - p2[0].y*p2[1].x)) / denom,
                      ((p1[0].x*p1[1].y - p1[0].y*p1[1].x)*(p2[0].y - p2[1].y) -
                       (p1[0].y - p1[1].y)*(p2[0].x*p2[1].y - p2[0].y*p2[1].x)) / denom);

    return intersect;
}

vector<Point2f> lineToPointPair(Vec2f line)
{
    vector<Point2f> points;

    float r = line[0], t = line[1];
    double cos_t = cos(t), sin_t = sin(t);
    double x0 = r*cos_t, y0 = r*sin_t;
    double alpha = 1000;

    points.push_back(Point2f(x0 + alpha*(-sin_t), y0 + alpha*cos_t));
    points.push_back(Point2f(x0 - alpha*(-sin_t), y0 - alpha*cos_t));

    return points;
}

Надеюсь, что это поможет!

Ответ 2

Сканирование по нескольким строкам изображения, например, 1/4 пути вниз, поиск центра красных пикселей. Затем повторите для ряда рядом с нижним - например, 3/4 пути вниз. Это дает вам две точки на вертикальной панели

Теперь повторяем для двух столбцов рядом с краем изображения - например, 1/4 и 3/4 поперек - это дает вам две точки на горизонтальной части.

Простое одновременное уравнение дает вам точку пересечения.

Если это видеопоследовательность, и вы очень затягиваете время, вы можете использовать точки, найденные вами в предыдущем кадре, и искать небольшое окно вокруг этой точки - при условии, что крест не сильно изменился.

пс. Если линии не прямые или переходят к случайным углам между кадрами, или вам нужна доля пиксельной точности, есть лучшие методы.

Ответ 3

Hough Lines должен помочь вам там, и это также достаточно хорошо в более сложных ситуациях.

Итак, вы можете

  • Фильтр gauss/медиана (необязательно)
  • Коня или сегментация. Я рекомендую вам сегментирование. Это даст вам гораздо больше строк, и следующие шаги потребуют больше, но точность будет субпикселем
  • Шумные линии (классические). резюме:: HoughLines(); Он вернет несколько строк, описываемых rho и theta. (если вы используете сегментацию, могут быть hundreads)

  • для каждой пары из них, которые не принадлежат к одной и той же красной строке (abs (theta1-theta2) > minTheta), вычислить пересечение. Здесь нужна некоторая геометрия

  • усредняют эти центры по х и у. Или используйте некоторые другие статистические данные для получения средней центральной точки.

Вот пример использования, с которого вы можете начать. Обязательно измените препроцессор #if 0 на #if 1, чтобы использовать классическое преобразование.