为Delphi翻译OpenCV头文件的想法来源于最初的一个梦想......
我想做一个类似于机器人系统的东西,带有两个可移动的摄像头,能够跟踪(直接“凝视”)给定物体并确定与物体的距离。那是 2012 年。但由于我更像是一名程序员而不是硬件工程师,这一切都是从当时现有算法的实现开始的。很快就意识到算法及其实现不是目标。目标是一个机器人系统。因此,决定使用现有的图像处理库。但是,不幸的是,在 Object Pascal 上,当时发现的现成算法库无法解决这些任务。
我非常喜欢 OpenCV,然后还有 2.4.2 版本,但当然没有 Object Pascal 支持。其他作者尝试翻译 OpenCV 头文件的尝试并不多,只是作为一个例子,概念。我记得很清楚,如果你想要 Java,它让我很生气——这里是一个端口,一个用于创建接口类的工具,对程序员不可见,将调用转发到 OpenCV,最重要的是,几乎是一对一的Java 中的 C++ 类型的程序。这与 Python 和其他一些语言几乎相同。简而言之,Delphi主义(正常人中女权主义的类似物)接管了。
由于还是OpenCV 2.4.2,加上C(cdecl)x32函数,所以翻译头文件的工作原来是挺简单的……大,但是简单,很庞大,但是简单……例程,但是简单。 .. 只需添加“cdecl; 外部核心库;“ 并重新排列参数名称及其类型。甚至有人尝试创建自己的自动广播工具。当时可用的现成工具制作游戏,然后编辑比手动翻译需要更长的时间。更重要的是,OpenCV 头文件不是“Hello world”——关于如何将作者的想法正确地转换为 Object Pascal 代码,必须做出很多决定。例如,由于 Var 参数返回方式的不同,cvGetSize 函数必须实现为交换寄存器的汇编程序插入。有些事情没有最终确定,例如,将 cvImage 转换为 TBitmap(cvImage2Bitmap 函数)——并非所有可能的图像格式都实现了,但这并不重要。在某个时候,我们有两个人。发表后关于 Habré 的文章,Mikhail Grigoriev (@Sleuthhound) 加入了该项目。一般来说,OpenCV API 的整个范围直到版本 2.4.13 都已翻译。然后“森林阿甘”开启了——我们跑到了这个地方,但我们能不能跑得更远。有 OpenCV + OpenGL、OpenCV + SDL、OpenCV + FFMPEG 的示例。FFMPEG x32 甚至有自己的头文件翻译。找到并翻译了当时互联网上存在的所有使用 OpenCV 的 C 程序示例。添加了在 FPC 中使用所有这些的功能。Delphi的组件已经开发完成,貌似“不是每个人”都能安装。未发布组件的自动安装程序......但为什么呢?
因为 OpenCV 3.0 出来了。那么,你为什么要这样做......类,隐式转换......一般来说 - x64。Object Pascal 当时无法做到这一点。Delphi主义已经进入激进Delphi主义阶段。
有很多尝试将 C++ 类转换为 Object Pascal。起点是在 Delphi 中使用 C++ 对象的文章。它是基于两种思路开发的一个代理dll。在第一种情况下,对 Object Pascal 的调用被简单地转换为对 OpenCV 类函数的调用,在第二种情况下,COM 接口在代理 dll 中开发,其引用返回给调用程序。这些尝试的残余可以在source3目录中看到。这两种方法的结果具有可比性。
最初不喜欢这种方法的主要内容:
-
需要“包装”所有的类,这是很多的,在这种情况下,非常困难。
-
OpenCV 库本身对于隐式类型转换和许多其他参数的所有灵活性都完全丧失了。
已经编写了几个工具来自动翻译第一个和第二个选项的 OpenCV 类。在其中一个选项中,llvm 用于解析自己开发的 llvm 头文件到 Object Pascal 的翻译。尝试在 Object Pascal 中从 Java 编写 JNI 的类似物。最终,由于时间不够和需要定期吃饭和睡觉,在 Object Pascal 中重新发明 C++ 的尝试失败了......
好吧,正如机器人所说的(不是操作系统),“谁记得旧的,他的相机会坏掉......”。
我想要从 OpenCV 到 Object Pascal 最灵活地使用 OpenCV 库的功能,包括显式和隐式类型转换、对 OpenCV 本身的隐藏吸引力、标准 Object Pascal 构造的使用以及它们对程序员隐藏的转换C++ 格式。Object Pascal 实现这些想法的一些功能早在 Delphi 2005 中就已经存在(如果我没记错的话):类运算符、隐式、显式。但这还不够。在任何情况下,都需要为 Object Pascal 中的类和记录调用构造函数和析构函数,例如 Init 和 Done。对于接口 - 毕竟,它们必须以某种方式创建。那些。在 C++ 中没有这样的机制,自动创建和销毁自己的结构。
现在它发生了 - 自定义管理记录!此外,具有控制赋值的能力——类操作符Assign。通过调用初始化程序自动创建并通过调用“销毁器”来销毁结构。
阻碍工作的第二件事是在 Object Pascal 和 C++ 类的函数中将参数传递给 x32 的不同方式。我不得不调查每个函数(我们正在讨论 OpenCV 2.4.x 中的类)。随着 OpenCV 3.0 向 x64 的过渡,该问题得到了解决,在 Object Pascal 和 C++ 中具有统一的调用约定(但存在细微差别)。
第三个也是最重要的事情是从 Object Pascal 到 OpenCV 的类函数的调用。事实证明(很久以前),C++ 类的函数可以通过“装饰”名称导入,并且正确调用它们已经是一个技术问题。顺便说一句,中国海豚现在经常使用这种方法。
综合起来,这三个因素已经导致了在 Delphi 中使用 OpenCV 4.5.2 的尝试(例如DOpenCV)的出现,尽管再次作为一个概念,作为一个例子。
所以,一天早上醒来,我清楚地听到鸟鸣声,烧水壶的声音——“是时候了!”。然后还有这些意想不到的非工作日休息日。
现在,关于问题的本质。
1.从DLL中获取所有函数不是问题,例如opencv_world454.dll中有7320个(包括6930个函数和类操作符和390个函数)。装修也不是问题。了解这个类的函数是什么:constructor,destructor,operator也不是问题,我们看文档,或者我们可以通过解码后的名字自己梳理一下(这个比较难)。
例如,构造函数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';
请注意,第一个参数是指向类本身