Средства разработки

Конечно, обычно для компьютерного зрения (далее CV) обычно используют OpenCV и Python, но… Python не единственный язык программирования, на котором можно работать с этой мощной библиотекой. OpenCV доступна также для других языков, например для C++ и C#. Как вы могли уже понять, я большой фанат C#!

Для (в первую очередь) собственного удобства я буду использовать C# + WinForms для создания быстрого и понятного интерфейса. А также саму OpenCV в обертке OpenCvSharp.

Я не буду описывать, что должно находиться на форме в этом приложении, – уверена, вы решите сами, – поэтому сразу к делу!

Немного истории

Что же такое OpenCV? Это библиотека с открытым исходным кодом, предназначенная для задач компьютерного зрения и обработки изображений.

Её разработка началась ещё в 1999 году в исследовательском центре Intel, а первая полноценная версия вышла в 2000 году. Целью проекта было создание простой, быстрой и кросс-платформенной библиотеки, которую можно использовать как в научных проектах, так и в коммерческих продуктах.

С тех пор OpenCV стала практически де-факто стандартом в области CV – особенно для тех, кто делает первые шаги. За библиотекой стоит огромное сообщество, тысячи примеров, документации и туториалов.

Сегодня эта библиотека поддерживает обработку как изображений, так и видео, распознавание лиц и объектов, работу с камерами, автоматическую калибровку и многое, многое другое! Чудесно, правда?

Так как же все же работать со столь мощным инструментом?

Первые шаги

Самое важное, что нужно понимать новичку для работы с CV на любом языке программирования, – алгоритм действий, когда у тебя под рукой (к твоему счастью) есть готовая библиотека. Да, OpenCV содержит огромное количество функций и методов, но для начала хватит… Меньше 10!

Хочется отметить, что можно и самому, вручную, без готовой библиотеки, написать такую систему CV, но это займет больше времени, да и знаний математики, алгоритмов нужно больше… Это не всегда то, с чего хочется начать новичку.

Но вернемся к делу! Какой же алгоритм? Очень простой:

Для начала нам надо подключить камеру. Советую это действие выносить в отдельный класс, методы которого вы будете вызывать в будущем, не стоит мешать все в одну несчастную форму. На этом этапе нам понадобится OpenCV VideoCapture:

VideoCapture videoCapture = new VideoCapture(1);

А тут внимание! Внутри конструктора (после new…) мы указываем НОМЕР камеры, которая имеется у вашего ПК или ноутбука. 0 – чаще всего встроенная, а дальше по подключению. Я использую камеру, которую подключила по USB, а значит ставлю 1 (других камер у меня нет…). Вы также можете создать массив «камер», если у вас их ну очень много, перебрать и выбрать первую рабочую… Ну, если очень хочется.

После создания, так сказать, камеры, вы можете считать изображение:

if (VideoCapture != null)
{
    VideoCapture.Read(Frame);
    if (Frame.Empty()) return null;

    Image = Frame.ToBitmap();
    return Image;
}

Главное, аккуратно! Постарайтесь избежать ошибок при попытках это сделать – проверьте, что у вас есть , а также, что полученный Frame не пуст.

Данный кусочек кода рекомендую поместить в метод, а метод, в свою очередь, поместить в цикл while(true) с флагом, либо в таймер – так вы сможете получать видео с камеры, а не одно изображение.

Самая магия

Для тех, кто позабыл, а что мы вообще делаем, напомню: мы делаем линейку. А значит нам нужно как-то с помощью камеры измерить какой-либо эталонный объект. Я возьму для этого простой бумажный квадратик со сторонами 5 на 5 см, достаточно яркий, чтобы его было легче распознать.

Наш эталонный образец
Наш эталонный образец

А что же нам надо от OpenCV? Пара-тройка методов для начала:

Cv2.CvtColor() – преобразует цвета нашего изображения. Например, переводит цветное изображение в чёрно-белое (оттенки серого), чтобы с ним было проще работать. Машинам, в отличие от нас, не особо важны цвета – им нужны границы, формы и контраст.

Cv2.Threshold() – превращает изображение в «чёрное и белое» (в буквальном смысле). Всё, что светлее порога, становится белым, всё остальное – чёрным. Это упрощает выделение нужных объектов, особенно если они контрастные.

Cv2.Canny() – находит границы объектов. Очень мощный инструмент, который помогает «увидеть» края, где резко меняется яркость. Это как контурный карандаш для компьютера: очерчивает всё, что может быть объектом.

Cv2.FindContours() – как только границы найдены, этот метод ищет на изображении замкнутые контуры. По сути, это способ сказать: «Вот, смотри, это – объект!»

С помощью этих методов мы можем достать из изображения или видео нужный нам объект, например, наш квадратик. А после по нему мы должны откалибровать нашу линейку, чтобы измерения были плюс-минус точными. Этот проект не предполагает, что все будет работать четко и идеально, как на производстве! Но для начала погрешность в ~0.05 мм вполне недурно!

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

Для начала нам нужно упростить контур (аппроксимировать) до более четкой и простой геометрической формы. Если на выходе мы получаем 4 точки, то объект похож или является квадратом (это нам и нужно!).

Далее необходимо отсортировать углы, потому что они могут быть расположены… неправильно. А нам нужно четкое выравнивание для более точных измерений.

После сортировки мы как раз-таки выравниваем изображение. Тут понадобится математика – немного матриц. Но можете выдохнуть, она совсем маленькая:

Point2f[] dstPoints = new Point2f[]
{
    new Point2f(0, 0),
    new Point2f((float)(realSizeMm * pixelsPerMm), 0),
    new Point2f((float)(realSizeMm * pixelsPerMm), (float)(realSizeMm * pixelsPerMm)),
    new Point2f(0, (float)(realSizeMm * pixelsPerMm))
};

Дело в том, что мы должны получить идеально ровное изображение, словно мы смотрим четко вертикально сверху на объект, хотя в реальности это не так.

Для более динамичной работы приложения, я предлагаю подбирать количество пикселей на 1 мм вручную, буквально настраивая линейку. Так мы откалибруем ее вручную, можно даже до идеала! И в итоге получим наш секретный коэффициент.

Ну, и не забываем про визуализацию! Рисуем контур нашего квадратика, да и вообще любого объекта.

А расчет размера нового объекта уже совсем несложный: находим контуры, как делали выше, а потом берем от наибольшего контура наименьший прямоугольник. Это нужно для более точного измерения, как и все, что мы делали выше. После чего просто считаем, деля на наш коэффициент полученные ширину и высоту прямоугольника.

RotatedRect minRect = Cv2.MinAreaRect(biggestContour);

double widthMm = minRect.BoundingRect().Width / CalculatedCoeff;
double heightMm = minRect.BoundingRect().Height / CalculatedCoeff;

Теперь можно измерять разные квадратики!

Распознавание квадратика
Распознавание квадратика
Калибруем...
Калибруем...
Измеряем!
Измеряем!
И еще...
И еще...
И еще!
И еще!
Ну и проверка измерений
Ну и проверка измерений

Заключение

Что ж, это все, что я хотела рассказать и показать вам в этой статье! Надеюсь, кому-то это поможет, а, возможно, кому-то захочется повторить проект.

Не бойтесь пробовать что-то новое, даже если это кажется сложным и непонятным! Программирование таких вещей – это супер интересно!

Комментарии (10)


  1. Daddy_Cool
    06.08.2025 22:31

    Очень интересно! А можно определить скорость движения точки причем по окружности?


    1. reoliss Автор
      06.08.2025 22:31

      Конечно можно! На самом деле с помощью CV можно реализовать практически все, о чем можно подумать, так как в принципе его используют во многих сферах.
      А такую задачу решить довольно просто – находим яркую точку, которая движется по кругу, фиксируем центр окружности, а также повороты точки относительно этого центра. Имея эти данные, можно посчитать угловую скорость. Да и линейную, если четко знаем радиус окружности.


      1. Daddy_Cool
        06.08.2025 22:31

        Вот кто бы мне это написал... )))

        Мы делали сами - через расчет корреляции, но точность не ахти получалась.


        1. reoliss Автор
          06.08.2025 22:31

          Ну, зато полезный опыт – умение делать самостоятельно)))


  1. n0isy
    06.08.2025 22:31

    Ох. Сколько вам ошибок трудных ещё такой метод сделает. Вы даже не рассчитали искажения в камере, хотя OpenCV позволяет их высчитать через checkboard и исказить обратно.


    1. reoliss Автор
      06.08.2025 22:31

      Спасибо за это уточнение!
      Это действительно так, однако эта статья про более простой проект, который можно реализовать в качестве знакомства с OpenCV. Я, думаю, что затрону такой метод калибровки через доску, так как знаю прекрасно его эффективность)


  1. DASpit
    06.08.2025 22:31

    Стоит отодвинуть камеру подальше и ваш коэффициент станет нерелевантным. В opencv есть методы бинокулярного зрения с калибровкой по шахматной доске, там возможно получить более адекватный результат.


    1. reoliss Автор
      06.08.2025 22:31

      Именно по этой причине я добавила в приложение возможность вручную подобрать коэффициент с помощью ползунка)
      Более точный результат действительно можно получить через более точные методы и встроенную в OpenCV математику, но этот проект супер простой и супер примитивный) Это его суть, так как я пока не пишу для продвинутых пользователей, а делюсь простыми вещами.
      Но спасибо за ваш комм) Я обязательно затрону и такие методы в других статьях)


  1. Newm
    06.08.2025 22:31

    Мне реально интересно, есть ли возможность в OpenCV с помощью 3 камер и эталонной фигуры (возможно нескольких фигур) высчитывать размеры реальных объектов в пространстве? С точки зрения математики задача реальная. А вот реализована ли она в библиотеке?


    1. reoliss Автор
      06.08.2025 22:31

      Вообще, подобное вполне можно реализовать, хотя и не очень легко. В библиотеке есть ряд методов, позволяющих калибровать и работать с несколькими камерами, например, Cv2.StereoCalibrate().
      Так что все вполне реально)) Особенно, если захочется запариться)