Сначала была мечта…

Что-то похожее на робототехническую систему, с двумя подвижными камерами, способностью отслеживать (направлять «взгляд» на) заданный объект и определять расстояние до объекта. И это был 2012 год. Но так как я больше программист нежели железячник, то все началось с реализации существующих в то время алгоритмов. Скоро пришло осознание, что алгоритмы и их реализация не есть цель. Цель – робототехническая система. Поэтому было принято решение воспользоваться существующими библиотеками обработки изображений. Но, к сожалению, на Object Pascal готовые библиотеки алгоритмов, которые были найдены в то время, не позволяли решать поставленные задачи.

Очень понравилась OpenCV, тогда еще версия 2.4.2, но естественно без поддержки Object Pascal. Попытки других авторов трансляции заголовочных файлов OpenCV были скромными, просто как пример, концепция. Помню очень тогда взбесило, хотите Java – вот пожалуйста порт, инструменты для создания интерфейсных классов, незаметный для программиста проброс обращений в OpenCV и, самое главное, практически один в один с С++ вид программы на Java. Почти тоже самое с Python и некоторыми другими языками. Короче говоря, Дельфизм (аналог феминизма у нормальных людей) взял верх.

Так как это была еще версия OpenCV 2.4.2, с С (cdecl) x32 функциями, то работа по трансляции заголовочных файлов оказалась достаточно простой… большой, но простой, очень объемной, но простой… рутинной, но простой… Просто добавь «cdecl; external core_lib;» и переставь местами имя параметра и его тип. Даже была попытка создать свой автоматизированный инструмент для трансляции. Доступные в то время готовые инструменты выдавали дичь, которую потом править было дольше, чем переводить вручную. Ну и тем более заголовочные файлы OpenCV это не "Hello world" – очень много приходилось принимать решений о том, как правильно перевести задумки авторов в код на Object Pascal. Например, в силу различий способов возврата Var параметров функцию cvGetSize пришлось реализовывать в виде ассемблерной вставки, которая меняет местами регистры. Некоторые вещи не доработаны, например, конвертация cvImage в TBitmap (функция cvImage2Bitmap) – реализованы не все возможные форматы изображений, но это не критично. В какой-то момент нас стало двое. После публикации статьи на Хабре к проекту подключился Михаил Григорьев (@Sleuthhound). В целом, весь объем API OpenCV вплоть до версии 2.4.13 был переведен. Потом включился «Форест Гамп» - добежали до этого места, а сможем ли побежать дальше. Появились примеры с OpenCV+OpenGL, OpenCV+SDL, OpenCV+FFMPEG. Для FFMPEG x32 даже была сделана собственная трансляция заголовочных файлов. Были найдены и переведены все существующие в интернете в то время примеры программ на С, использующих OpenCV. Добавлена возможность использовать все это в FPC. Разработаны компоненты для Delphi, которые похоже «не только лишь каждый» может установить. Автоматический инсталлятор компонент, не доведенный до релиза … А все почему?

Да потому что вышел OpenCV 3.0. Ну зачем вы так… Классы, неявные преобразования… Да и вообще – x64. Object Pascal в то время так не мог. Дельфизм перешел в стадию радикального Дельфизма.

Было предпринято множество попыток транслировать С++ классы на Object Pascal. Отправным пунктом послужила статья Using C++ objects in Delphi. В основе лежат две идеи основанные на разработки прокси dll. В первом случае вызовы Object Pascal просто транслировались в вызовы функций класса OpenCV, во втором – в прокси dll разрабатывались COM интерфейсы, ссылка на которые возвращалась в вызывающую программу. Остатки этих попыток можно наблюдать в каталоге source3. Оба подхода сопоставимы по получаемому результату.

Основное, что изначально не понравилось в таких подходах:

  1. Требовалось «обернуть» все классы, что очень много и, в данном случае, очень сложно.

  2. Напрочь терялась вся гибкость самой библиотеки OpenCV по неявному преобразованию типов и еще по многим другим параметрам.

Были написаны несколько инструментов для автоматической трансляции OpenCV классов как для первого, так и для второго варианта. В одном из вариантов, для парсинга использовался llvm с собственной разработкой перевода заголовков llvm на Object Pascal. Была реализована попытка написания в Object Pascal аналога JNI из Java. В конечном итоге, из-за недостатка времени и необходимости периодически кушать и спать, попытка изобрести С++ в Object Pascal тогда не удалась…

Ну да ладно, как говорят андроиды (не ОС) «Кто старое помянет, у того камера сломается…».

Что хотелось от OpenCV на Object Pascal – это максимально гибкое использование возможностей библиотеки OpenCV, с явным и неявным преобразованием типов, скрытое обращение к самой OpenCV, использование стандартных конструкций Object Pascal и их скрытое от программиста преобразование в формат C++. Часть возможностей Object Pascal для реализации этих идей существовало еще в Delphi 2005 (если не ошибаюсь): class operator, Implicit, Explicit. Но этого было мало. В любом случае требовалось для класса в Object Pascal вызывать конструктор и деструктор, а для записи (record), например, Init и Done. Для интерфейсов – все-таки их надо было как-то создавать. Т.е. такого механизма как в C++, автоматического создания и уничтожения собственных структур пока не было.

И вот свершилось - Custom Managed Records! Да еще с возможностью контроля присвоения - class operator Assign. Автоматическое создание с вызовом инициализатора и уничтожение структуры с вызовом «уничтожатора».

Второе, что сдерживало работу – это разный способ передачи в x32 параметров в функции для Object Pascal и в классах С++. Приходилось исследовать каждую функцию (речь о классах еще в OpenCV 2.4.х). Проблема решилась с переходом OpenCV 3.0 на x64, с единым соглашением о вызовах в Object Pascal и С++ (но есть нюанс).

Третье и может самое быть главное – обращение из Object Pascal к функциям класса из OpenCV. Как это выяснилось (достаточно давно) функции класса С++ можно импортировать по «декорированному» имени, а правильно их вызвать это уже дело техники. Кстати, дельфисты-китайцы сейчас очень часто этот способ используют.

В совокупности, эти три фактора уже привели к появлению попыток использования OpenCV 4.5.2 в Delphi (например, DOpenCV), правда опять же как концепция, как пример.

Так что, проснувшись одним утром, я явно услышал в щебете птиц, звуке закипающего чайника – «Пора!». Да и тут еще эти неожиданные нерабочие не выходные.

Теперь по сути вопроса.

1. Получить из DLL все функции не проблема, например, в opencv_world454.dll их 7320 штук (в том числе 6930 функций и операторов классов и 390 функций). Раздекорировать – тоже не проблема. Понять, что это за функция класса: конструктор, деструктор, оператор – тоже не проблема, смотрим документацию, ну или разбираемся сами по раздекорированному имени (это сложнее).

Например, конструктор cv::Mat(int rows, int cols, int type); имеет вид

??0Mat@cv@@QEAA@HHH@Z
public: __cdecl cv::Mat::Mat(int,int,int) __ptr64

2. Для импортированной функции пишем аналог на Object Pascal

Например, для того же конструктора

procedure Constructor_Mat(Obj: pMat; a: int; b: int; c: int);
external opencv_world_dll name '??0Mat@cv@@QEAA@HHH@Z';

Обратите внимание, что первым параметром передается указатель на сам класс (регистр RAX), что является стандартным подходом во многих языках программирования. Ну если честно, то это указатель на память с данными класса.

Передача параметров осуществляется согласно Microsoft x64 calling convention, ну почти. Одним из исключений является передача по значению не простых типов. Delphi, все не простые типы (например, записи) даже если они размером 1 байт передает как адрес, в тоже время передача по значению в классах С++ для данных размером меньше или равными 8 байт передаются в 64-битном регистре своим значением. Такое поведение наблюдается, например, для TRect<Int> (Object Pascal) и Rect=Rect2i=Rect_<int>(C++). Вообще, несмотря на соглашения о вызовах определяющих порядок использования регистров, в способе передачи данных из Object Pascal в функцию класса С++ есть несколько отличий, но, наверное, это тема отдельного разбирательства.

В некоторых случаях импортировать по имени не получается (надо разбираться, оставим это в TODO) и приходится импортировать по индексу, что плохо. При переходе на следующую версию будут проблемы.

3.Когда-то, в умной книжке по C++, было сказано, что в C++ классы – это структуры (struct), подвергшиеся сильной радиационной и химической мутации. А действительно, что выдумывать что-то новое? Память под данные класса С++ выделяем на стадии компиляции, в месте его объявления вызываем конструктор по умолчанию (это если класс объявлен без параметров), когда не нужен – вызываем деструктор. Красота!

И вот здесь и пригодились Custom Managed Records. Их поведение ну очень уж похоже на классы в C++.

TMat = record // 96 bytes, v4.5.4
  public
    // default constructor
// Mat();
    class operator Initialize(out Dest: TMat); 
    class function Mat(): TMat; overload; static;
    class function Mat(rows, cols, &type: Int):TMat; overload; static;
// Mat(Size size, int type);
    class function Mat(const size: TSize; &type: Int): TMat; overload; static; 
// Mat(const Mat& m, const Rect& roi);
    class function Mat(const m: TMat; const roi: TRect): TMat; overload; static;  
    function Mat(const roi: TRect): TMat; overload; 
// ~Mat();
    class operator Finalize(var Dest: TMat);
    class operator assign(var Dest: TMat; const [ref] Src: TMat); 
// Mat& operator = (const MatExpr& expr);
    class operator Implicit(const m: TMatExpr): TMat;
// CV_NODISCARD_STD static Mat diag(const Mat& d);
// Mat diag(int d=0) const; 
    function diag(d: Int = 0): TMat;  
// CV_NODISCARD_STD Mat clone() const;
    function clone: TMat;  
// CV_NODISCARD_STD static MatExpr zeros(int rows, int cols, int type);
    class function zeros(const rows, cols: Int; &type: Int): TMatExpr; overload; static; 
// CV_NODISCARD_STD static MatExpr zeros(Size size, int type);
    class function zeros(const size: TSize; &type: Int): TMatExpr; overload; static; 
// CV_NODISCARD_STD static MatExpr ones(int rows, int cols, int type);
    class function ones(rows: Int; cols: Int; &type: Int): TMatExpr; overload; static; 
// CV_NODISCARD_STD static MatExpr ones(int ndims, const int* sz, int type);
   class function ones(ndims: Int; const sz: pInt; &type: Int): TMat; overload; static; 
// void create(int rows, int cols, int type);
    procedure Create(rows, cols, &type: Int); overload;  
// void create(Size size, int type);
    procedure Create(size: TSize; &type: Int); overload; 
// void addref();
    procedure addref;  
// void release();
    procedure release; 
// bool isContinuous() const;
    function isContinuous: BOOL;  
    // //! returns true if the matrix is a submatrix of another matrix
// bool isSubmatrix() const;
    function isSubmatrix: BOOL;          
// size_t elemSize() const;
    function elemSize: size_t;           
// size_t elemSize1() const;
    function elemSize1: size_t;          
// int type() const;
    function &type: Int;                 
// int depth() const;
    function depth: Int;                 
// int channels() const;
    function channels: Int;              
// size_t step1(int i=0) const;
    function step1(i: Int = 0): size_t;  
// bool empty() const;
    function empty: BOOL;                
// size_t total() const;
    function total: size_t; overload;    
// size_t total(int startDim, int endDim=INT_MAX) const;
    function total(startDim: Int; endDim: Int = INT_MAX): size_t; overload;  
// int checkVector(int elemChannels, int depth=-1, bool requireContinuous=true) const;
    function checkVector(elemChannels: Int; depth: Int = -1; requireContinuous: BOOL = true): Int;  

    class operator LogicalNot(const m: TMat): TMatExpr; 
    function at<T>(i0: Int): T; 
  public const
    MAGIC_VAL       = $42FF0000;
    AUTO_STEP       = 0;
    CONTINUOUS_FLAG = CV_MAT_CONT_FLAG;
    SUBMATRIX_FLAG  = CV_SUBMAT_FLAG;

    MAGIC_MASK = $FFFF0000;
    TYPE_MASK  = $00000FFF;
    DEPTH_MASK = 7;
  public
// int flags;
    flags: Int; 
    // ! the matrix dimensionality, >= 2
// int dims;
    dims: Int; 
    // ! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
// int rows, cols;
    rows, cols: Int; 
    // ! pointer to the data
// uchar* data;
    Data: pUChar; 
    //
    // ! helper fields used in locateROI and adjustROI
// const uchar* datastart;
    datastart: pUChar; 
// const uchar* dataend;
    dataend: pUChar;   
// const uchar* datalimit;
    datalimit: pUChar; 
    //
    // ! custom allocator
// MatAllocator* allocator;
    allocator: pMatAllocator; 
    // ! and the standard allocator
    //
    // ! interaction with UMat
// UMatData* u;
    u: pUMatData; 
    //
// MatSize size;
    size: TMatSize; 
// MatStep step;
    step: TMatStep; 
  end;

Размер записи TMat в точности равен размеру данных класса Mat, более того, если класс в OpenCV не содержит виртуальных функций и удается найти описание всех его полей, в том числе и protected, то мы получаем полную аналогию и доступ ко всем полям класса.

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

В случае, если лень искать поля класса, можно просто в теле record создать

Data: array [0 .. 31] of Byte;

как это было сделано для std::vector<T> и оставлено на «TODO». Любой вектор C++ в памяти занимает 32 байта (хотя может и ошибаюсь).

При создании записи (в месте объявления) вызывается

class operator TMat.Initialize(out Dest: TMat);
begin
  Constructor_Mat(@Dest);
end;

При уничтожении

class operator TMat.Finalize(var Dest: TMat);
begin
  Destructor_Mat(@Dest);
end;

Красота! Но есть нюанс.

Иногда в С++ делается следующая штука

Mat r = Mat(10, 3, CV_8UC3);

т.е. сразу вызывается нужный конструктор.

В Object Pascal конструктор будет вызываться два раза

Var r:TMat := TMat.Mat(10, 3, CV_8UC3);

при объявлении и при присвоении (еще и копирование Mat в class operator Assign), что замедляет работу.

Много восторга принесло работающее в Object Pascal неявное преобразование типов, например, из TMat в TInputArray или цепочка TMat->TMatExpr->TMat->TInputArray. Дизассемблированный код Object Pascal стал очень похож на дизассемблированный код C++.

Taк же спасают record helper. Если в С++ можно в заголовочных файлах наобъявлять структур и типов, а потом спокойно и где угодно их использовать, то в Object Pascal так нельзя. Выход из положения - дополнить функциональность в record helper где-нибудь в конце, после всех объявлений.

TMatHelper = record helper for TMat
  Public
// Mat(int rows, int cols, int type, const Scalar& s);
    class function Mat(rows, cols, &type: Int; const s: TScalar): TMat; overload; static;  
// void copyTo( OutputArray m ) const;
    procedure copyTo(m: TOutputArray); overload;          
// void copyTo( OutputArray m, InputArray mask ) const;
    procedure copyTo(m: TOutputArray; mask: TInputArray); overload; 
    class operator Subtract(const m: TMat; const s: TScalar): TMatExpr; 
// Mat& operator = (const Scalar& s);
    class operator Implicit(const s: TScalar): TMat; 
  end;

4. Неприятной особенностью некоторых функций и функций классов OpenCV является использование std:: классов. Если с std::string удалось справиться достаточно легко – создана opencv_delphi454.dll, которая экспортирует

class BODY_API ExportString : public cv::String {};

и дальше аналогично классам OpenCV, то вот с std::vector<T> пришлось повозиться. Сейчас реализован не оптимальный, корявый вариант.

enum VectorType {
         vtMat = 1,                      // vector<Mat>
         vtRect = 2,                     // vector<Rect>
         vtPoint = 3,           // vector<Point>
         vtVectorMat = 4,  // vector<vector<Mat>>
         vtVectorRect = 5,  // vector<vector<Rect>>
         vtVectorPoint = 6 // vector<vector<Point>>
};

Ну и, например, для создания

BODY_API void CreateStdVector(void* obj, int vt)
{
  if (vt) {
    switch (vt) {
     case vtMat:
       *(static_cast<vector<Mat>*>(obj)) = vector<Mat>();
       break;
     case vtRect:
       *(static_cast<vector<Rect>*>(obj)) = vector<Rect>();
       break;
     case vtPoint:
       *(static_cast<vector<Point>*>(obj)) = vector<Point>();
       break;
     case vtVectorMat:
       *(static_cast<vector<vector<Mat>>*>(obj)) = vector<vector<Mat>>();
       break;
     case vtVectorRect:
       *(static_cast<vector<vector<Rect>>*>(obj)) = vector<vector<Rect>>();
       break;
     case vtVectorPoint:
       *(static_cast<vector<vector<Point>>*>(obj)) = vector<vector<Point>>();
       break;
       }
    }
}

Более элегантная реализация отложена в TODO. Если уже существуют решения – подскажите.

Хотя в Object Pascal получилось красиво и это работает.

TStdVector<T> = record
  private
{$HINTS OFF}
    Data: array [0 .. 31] of Byte;
{$HINTS ON}
    class function vt: TVectorType; static;
    function GetItems(const index: UInt64): T;  public
    class operator Initialize(out Dest: TStdVector<T>);
    class operator Finalize(var Dest: TStdVector<T>);
    function size: Int64;

    function empty: BOOL; 
    procedure push_back(const Value: T); 
    property Items[const index: UInt64]: T read GetItems; default;
  end;

Плохо только в

class function TStdVector<T>.vt: TVectorType;
Var
  TypeName: String;
begin
  TypeName := GetTypeName(TypeInfo(T));
  if SameText('TMat', TypeName) then
    vt := vtMat
  else if SameText('TRect_<System.Integer>', TypeName) then
    vt := vtRect
  else if SameText('TPoint_<System.Integer>', TypeName) then
    vt := vtPoint
  else if SameText('TStdVector<opencv_world.TPoint_<System.Integer>>', TypeName) then
    vt := vtVectorPoint
  else
    Assert(false);
end;

Пока оставлено в TODO.

Заключение

  1. Сложилось впечатление, что OpenCV реализуется немножко сумасшедшими людьми в части организации кода. Иногда разбираешься как там все вызывается, преобразуется и думаешь: «Ну зачем так сложно-то?». В тоже время получившийся результат дает низкий уровень вхождения конечного пользователя. Подаешь на вход функции все, что угодно, а она это кушает и скрыто преобразовывает к нужному виду.

  2. В части создания интерфейса для Object Pascal, в целом, можно сказать, что получается. Не все, конечно, удастся реализовать один в один. Например, итераторы в std::vector – это еще та штучка, да и не хочется их вытаскивать только для того, чтобы реализовать один из вариантов конструктора std::vector. Однако примеры использования на C++ переписанные на Object Pascal очень похожи на оригинал, что позволит портировать большинство готовых конструкций и работающих программ.

  3. Скорее всего код будет еще неоднократно подвергаться рефакторингу в том числе и по способам взаимодействия с OpenCV. Кроме того, переведены доли процента объема OpenCV, а количество строк opencv_world.pas без включений уже перевалило за 4700. Конечно, там много комментариев, но часть из них станет кодом. Наверное, все-таки нужно будет разделять на отдельные модули исходя из деления modules в OpenCV.

  4. В конечном итоге будет разработана надстройка, позволяющая использовать родные для Object Pascal типы и структуры, например, TArray<T>, TList<T>. Хотя в последнем я сомневаюсь – слишком уж он громоздкий. Ну и, конечно, компоненты – нам тоже нужен низкий порог входа.

  5. Нужны советы по поводу организации opencv_delphi454.dll, так как я в С++ не Proficiency. Думаю, что знающим людям станет плохо, если они посмотрят код delphi_export.h.

  6. Ну и я не отказываюсь от помощи и советов по организации всего кода. Работа большая, интересная и чем-то похожа на охоту или рыбалку – азарт.

  7. И что же с мечтой? Скажу так - иногда путь к мечте интересней ее осуществления.

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


  1. TortIriska
    14.11.2021 19:11
    +4

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


    1. Laax Автор
      14.11.2021 19:15

      Надеюсь на советы знатоков...
      А так - у каждого своя мечта и путь свой...


  1. Sleuthhound
    14.11.2021 21:30
    +2

    @Laax привет ;)

    Моя эпопея с попыткой работы с OpenCV в Delphi завершилась как только мне понадобилось собрать программу под разные встраиваемые решения, под которые компилятора Object Pascal увы нет и не будет. Поэтому немного погоревав я достал с полки толстую книгу по Си, освежил память и начал писать на Си. Ну а Delphi... он хорошо там где нужно.


    1. HemulGM
      15.11.2021 11:38

      "под разные встраиваемые решения"
      А что за решения, например? Lazarus имеет компиляторы под разное всякое


  1. devzona
    15.11.2021 04:59
    -1

    Потрясно в наши дня видеть последователей клуба Delphi. Но время идет, одни инструменты умирают, другие рождаются. Почему бы не попробовать решить свою задачу на Python или на C# .NET? Там не потребуется делать обертки. Все работает из коробки и посты есть на Хабре. Я начинал программировать с QBasic и Visual Basic 6.0 из далекого 98`. Недавно вышел .NET 6, теперь буду переводить проекты на новую версию. Никакого нет желания использовать древние инструменты. Раскачайте свой мозг, перейдите на современные инструментальные средства, а Object Pascal поставьте в рамочку для истории, детям рассказывать про истоки программирования в Древней Руси.


    1. HemulGM
      15.11.2021 11:45
      +1

      Для питона и шарпа тоже кто-то делал обёртки. Так в чем же проблема и преимущество?


      1. devzona
        15.11.2021 22:42
        -1

        В том что не нужно изобретать велосипед. Текущие обертки поддерживаются сообществом и своевременно обновляются. Так же под Python и C# есть готовый код, который можно просто взять и использовать в своем проекте. Если вы свой проект опубликуете на GitHub, то он будет более полезен сообществу в отличие от проекта на Pascal, т.к. количество людей пишущих под Pascal стремится к нулю.


        1. HemulGM
          15.11.2021 22:58

          Так всё же в чем отличие? И по какой причине я не могу опубликовать свой код на Pascal не гите?

          Чем отличается проект, поддерживающийся сторонним разработчиками на шарпе и питоне, от проекта поддерживающегося сторонними разработчиками на Паскале?

          Он так же доступен всем, его так же используют и так же улучшают.
          Почему не использовать, например, OpenCV на том языке, на котором написан OpenCV? Идёт из коробки же и не нужно ни каких обёрток.

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

          На Паскале есть обёртки ко многим вещам, например для работы с CUDA или Vulkan и успешно используются разработчиками.


    1. boboycha
      15.11.2021 17:19

      Читаю такие комменты и поражаюсь... ЗАЧЕМ в МОЕМ мечете твои исповеди???
      Тут мужики стараются, делают, а кто-то, который нефиг тут делать приходить и начинает учить уму разуму....


      1. devzona
        15.11.2021 22:54
        -1

        Поздравляю Вас с Вашим первым комментарием. Мужики молодцы, но почему бы не сфокусировать свое внимание на "Что-то похожее на робототехническую систему, с двумя подвижными камерами, способностью отслеживать (направлять «взгляд» на) заданный объект". Сдается, что целью не является реализация робототехнической системы так таковой. Нет никакой постановки задачи. Про эту систему ровным счетом ничего не написано. Безусловное право автора заниматься в данном направление, но разве не хотелось сделать более значимый проект?


  1. sshmakov
    15.11.2021 08:42

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

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


    1. Paskin
      15.11.2021 09:46
      +1

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


    1. Laax Автор
      15.11.2021 11:24

      В Delphi порог вхождения определяется наличием или отсутствием компонент.

      А замена универсальной функции набором функций с разными типами параметров – это тоже, только в профиль. Алгоритм один, на вход принимает данные в жестко заданном виде. Даже если определена функция, принимающая на вход данные в необычной форме, все равно их нужно будет преобразовать под формат алгоритма. А как это делается – неважно. Скрыто, как это делает OpenCV, или вызовом одной из специфических функций, которая все равно делает скрыто такие преобразования. Для пользователя проще скрытое преобразование.


      1. sshmakov
        15.11.2021 14:30

        Мы же говорим про пользователей Delphi? Много среди них настолько академических учёных, которые не заботятся о типах переменных?


        1. HemulGM
          15.11.2021 23:16

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


          1. devzona
            16.11.2021 02:17
            -2

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


            1. HemulGM
              16.11.2021 22:11

              В питоне нет ничего инновационного, кроме, наверное операторных скобок в виде отступов, в чем я сомневаюсь.

              Декораторы? Были и до питона

              Динамическая типизация - до питона

              Именованные параметры - до питона

              Сборщик мусора? Не смешите

              Интерпретируемый?

              Не указывать типы параметров - теперь моветон

              Так может уже начнёте аргументировать свою позицию?


              1. devzona
                17.11.2021 02:42
                -2

                Так Вы с себя и начните. Пруфлинки на научные статьи на паскале не завезли. Требуете от других, а сами свою позицию не подтвердили. И как к Вам проявлять уважение после этого? Если хотите аргументов, вот пожалуйста. На текущий момент Python находится на первой позиции в индексе TIOBE, а Delphi/Object Pascal на 17 месте. Ну давайте, порасказывайте что Python ничего не стоит и никому не нужен, чего не скажешь конечно же про Pascal. На питоне не пишу, использую C#. Еще важный момент, автор поста не указал архитектуру исполнения. Его робот будет работать на x86 или на ARM? Просто я тоже подготавливаю систему распознавания на .NET с использованием OpenCV на Linux. Система будет работать на одноплатном компьютере banana pi m64, процессор ARM. Датчики, GPIO уже умею подключать. Данные с Web-камеры из .NET кода забираю, по сути осталось прикрутить OpenCV, причем все это работает в Docker контейнерах. И что фантастического в использования OpenCV из паскаля?


                1. HemulGM
                  17.11.2021 07:43

                  Это вы вошли в топик по Паскалю и вы обвиняете язык в ненужности, а автора в глупости, но аргументировать должен начать я?

                  И да, Delphi собирается с нативной компиляцией под ARM, x86, arm64, m1 и так далее


                  1. devzona
                    17.11.2021 15:46

                    Я между прочем такого не заявлял, наглая и провокационная ложь. Мое предложение заключалось в использование более современных инструментов т.к. на мой взгляд это более рационально. Но это сугубо мое мнение, не претендующее на абсолют. В комментариях каждый высказывает свое мнение, и дело автора взять во внимание или нет. А за свои слова Вы между прочем не отвечаете, я все еще жду пруфлинки на современные научные публикации в которых расчеты выполнены на паскале.


                    1. HemulGM
                      17.11.2021 16:50

                      Ясно: "Я не я, и лошадь не моя".

                      К вашему сведению, Delphi, о котором идёт речь в посте 1986 года, а питон 1991 года. Т.е. разница в 5 лет.

                      Последняя версия Delphi (как спецификации языка, так и среды разработки) была выпущена в сентябре этого года. До этого, четверть года тому назад и так далее.

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


                      1. devzona
                        17.11.2021 17:44
                        -1

                        Это лишь говорит о том, что на Delphi кто то еще пишет, но это не делает его лидером. Вот когда в индексе TIOBE Delphi/Object Pascal хотя бы войдет в десятку, перейдет с жалкого 17 места, вот тогда и появится основание для пересмотра точки зрения. Я как вспомню что объявление переменных только в блоке VAR, так брр аж холодок по коже идет. Поверьте, я не считаю Delphi/Object Pascal плохим инструментом. В свое время на Delphi написал несколько программ для автоматизации медицинского сектора. Программы на Delphi меня кормили в прямом смысле этого слова, и я благодарен за это разработчикам. Но нужно вспомнить время шалтай-болтая когда часть разработчиков стали активно переходить на .NET. Время было потеряно, доверие сообщества было подорвано, и это привело к стагнации. Вы сравните стоимость IDE RAD Studio и MS Visual Studio, и вспомните когда появилась редакция Community Edition. Delphi/Object Pascal исторически устарел, сообщество в основном состоит из людей преклонного возраста, что тут еще добавить?


                      1. HemulGM
                        17.11.2021 17:53

                        RAD Studio Community Edition появилась несколько лет назад.

                        begin
                        --var Number: float := GetFloat;
                        --var Value := GetValue;
                        --var Func := function: integer
                        --------------begin
                        ----------------Result := 1;
                        --------------end;
                        --var GetNum := Func();
                        end;

                        Уже несколько лет как нормальный код.

                        И это только верхушка айсберга.


                      1. devzona
                        17.11.2021 18:02

                        RAD Studio Community Edition появилась несколько лет назад

                        Ха-ха, Вы уточните год первого выпуска и приведите ограничения лицензии для этой редакции


                      1. HemulGM
                        17.11.2021 18:23

                        И как лицензия говорит о возможностях языка? Больше похоже на уход от темы.