На существующем производстве была поставлена задача по разработке специализированного ПО. Для реализации ПО был выбран стек технологий:

  • Windows Forms – это платформа пользовательского интерфейса для создания классических приложений Windows от компании Microsoft. На платформе используется визуальный конструктор встроенный в Visual Studio и язык программирования С#;

  • OpenCV – это библиотека компьютерного зрения, которая будет использоваться для работы с изображениями. В данном случае выбран язык программирования С++ для работы с обновленной библиотекой.

Рассмотрим проблему на примере взаимодействия библиотеки OpenCV на языке С++ и технологии Windows Forms опирающуюся на язык программирования С# рассмотрим взаимодействия разных типов. А именно рассмотрим передачу изображения из типа Mat из OpenCV в тип Bitmap из Windows Forms. Передачу данных можно совершить тремя способами:

  • Через сохранение в файл;

  • Корректным чтением из другого типа;

  • Простейшими частями.

Первый способ это сохранение через файл. Суть данного метода состоит в том, чтобы сохранить изображение на устройстве пользователя в файле с определенным именем, а после считать его другим типом. В данном примере возможно сохранение данных класса cv::Mat функцией cv::imwrite в файле img.jpg, а после считывается функцией Bitmap::FromFile("img.jpg") переводя данные в объект класса Bitmap. Исходя из примера главная выгода данного метода это высокая скорость разработки данным методом. Недостатки это высокий расход ресурсов при преобразовании данных через файл. Также возможная потеря некоторых данных при переводе, вот в данном примере, если в объекте класса cv::Mat скаляр был четырехмерный, то при сохранении в файл скаляр станет трехмерным. Данный способ лучше использовать как временный для тестирования ключевых функций библиотек для конкретного выбора стека технологий.

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

Третий способ называется перевод данных простейшими частями. В данном случае простейшими частями изображения будет скаляр данных каждого пикселя. Проще говоря, перевод в данном способе происходит по каждому значению пикселя изображения. Поэтому является труд затратным при разработки приложения и по расходу ресурсов. Но при этом главным преимуществом является то, что после перевода данные объекта можно изменять, не нанося вреда изначальному объекту.

Из существующих способов был выбран второй, который полностью покрывает потребности проекта. Поскольку первый способ имеет высокий расход ресурсов, а третий способ предполагает избыточные действия. А так как мне нужно от Windows Forms только отображение изображения без его изменения. Второй вариант оказался самым предпочтительным в данном проекте.

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


  1. Darell_Ldark
    27.09.2024 20:18

    Не знаком с OpenCV, но, кажется, со стороны C# можно было вызывать методы из DLL?


  1. ideatum
    27.09.2024 20:18
    +4

    1. TerekhinSergey
      27.09.2024 20:18

      Есть ещё EmguCV (https://www.emgu.com/wiki/index.php/Main_Page). А то, что предлагает автор... ну мягко говоря, является изобретением велосипеда и не должно применяться в промышленных решениях практически никогда (пожалуй только если не стоит задача интеграции с дремучим легаси без поддержки - тогда придётся подстраиваться). Начинать же новый проект с лютых костылей - это путь в один конец

      Если стоит задача связать именно два приложения (а не встроить библиотеку), то есть следующие опции:

      • любое средство для удалённого вызова (тут можно остановиться на grpc или http) - по сути сразу получаем распределённую систему из коробки, которая может работать и локально тоже

      • именованные каналы, чистый сокет и т.п. (требует разработки собственного протокола обмена, поэтому сложно, да еще и платформозависимо в общем случае)

      • обмен через файл (имеет место быть в некоторых сценариях), тогда обычно родительский процесс вызывает дочерний с аргументами вида "пиши сюда", дожидается окончания работы и обрабатывает файл с результатом

      Для встраивания нативной библиотеки единственный промышленный путь - это использование PInvoke с маршаллингом типов (https://learn.microsoft.com/ru-ru/dotnet/standard/native-interop/pinvoke). В результате может получиться производительно, но в нетривиальных случаях придётся повозиться. Или, как написано выше, стоит обратить внимание на уже готовые обёртки, которые всю сложность вызова нативного кода прячут внутри


  1. rukhi7
    27.09.2024 20:18

    Можно приложение Win-Forms и на С++ написать-переписать и вообще никаких проблем не будет.


  1. rukhi7
    27.09.2024 20:18

    • Через сохранение в файл;

    • Корректным чтением из другого типа;

    • Простейшими частями.

    Вы бы почитали какую-нибудь документацию про возможности взаимодействия С# с С++ длл-ками, IPC,... Или посмотрели бы как люди делают, а то получается как в анекдоте: чукча не читатель, чукча писатель.


  1. Dotarev
    27.09.2024 20:18
    +1

    Мне кажется, Вам стоит изучить Программирование .NET с использованием C++/CLI.
    Предложенные ранее grpc, http, именованные каналы, чистый сокет и т.п (включая обмен через файл) - суть обмен через сериализацию/десериализацию.
    Вы пошли по правильному пути - прямое чтение из неуправляемой памяти. Как раз проблемы обмена с неуправляемой памятью из среды CLR и решает технология C++/CLI. Успехов!


    1. TerekhinSergey
      27.09.2024 20:18

      Вот только это windows-only решение. В Linux C++/CLI не завезли и не планируют. Там или PInvoke, или описанные выше решения