Средства разработки
Конечно, обычно для компьютерного зрения (далее 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;
Теперь можно измерять разные квадратики!






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