将OpenCV的代码从C++移植到C语言过程小记

2024-06-23 11:28

本文主要是介绍将OpenCV的代码从C++移植到C语言过程小记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

    • 1、需求背景
    • 2、移植成C语言
      • 2.0 移除namespace特性
      • 2.1 移除template特性
      • 2.2 移除class特性
      • 2.3 移除vector特性
      • 2.4 移除std函数
      • 2.5 移除引用传值
      • 2.6 移除auto类型推导
      • 2.7 C++中的关键词
      • 2.8 其他语言差异
    • 3、测试和杂项
      • 3.1 C语言和C++编译兼容
      • 3.2 GCC用C语言规则编译源文件
      • 3.3 修改文件名后缀
    • 4、最终代码

1、需求背景

最近有个项目要求将OpenCV的一个函数移植成由C语言编写,虽然我很不想干这件事情,但是毕竟是工作,还是得干。

本篇文章分享一下将C++代码移植成C语言的基本思路,和过程中遇到的问题。

2、移植成C语言

2.0 移除namespace特性

这个很简单,直接删。

2.1 移除template特性

C++的模板编程可以将多种类型的输入编译成分别对应的函数。

在C语言中,模板编程往往只会被解释成其中的一种。当然,如果被解释成多种,那就创建同样数量的类或函数就可以了。不同的类或函数可以增加不同的后缀以区分,例如 Point2f 表示 float 类型、Point2i 表示 int 类型。

C++代码:

template<typename _Tp> class Point_
{
public:_Tp x;_Tp y;Point_() : x(0), y(0) {}Point_(_Tp _x, _Tp _y) : x(_x), y(_y) {}Point_<_Tp> operator-(const Point_<_Tp>& other) {return Point_<_Tp>(x - other.x, y - other.y);}float cross(const Point_& other) const {return x * other.y - y * other.x;}
};typedef Point_<float> Point2f;

移除模板特性:

class Point2f
{
public:Point2f() = default;Point2f(float _x, float _y) : x(_x),  y(_y) {}float x = 0.f;float y = 0.f;Point2f operator-(const Point2f& other) const {return Point2f(x - other.x, y - other.y);}float cross(const Point2f& other) const {return x * other.y - y * other.x;}
};

2.2 移除class特性

C++的class主要实现了类属性内置方法重载操作符,这3个特性。

类属性可以用 typedef 的结构体实现。并且这一步可以不用一步到位,可以在所有的类方法逐渐移植完毕之后,最后再做修改。

内置方法我改为同前缀的一个一个单独的函数,将自己的结构体传入又传出。

重载运算符我采用Python命名风格,例如对减号的操作符重载,实现为 Point_sub 这样的形式。

C++代码:

// 定义类属性、重载运算符、和类方法
class Point2f
{
public:Point2f() = default;Point2f(float _x, float _y) : x(_x),  y(_y) {}float x = 0.f;float y = 0.f;Point2f operator-(const Point2f& other) const {return Point2f(x - other.x, y - other.y);}float cross(const Point2f& other) const {return x * other.y - y * other.x;}
};// 调用
Point2f p1, p2;
Point2f p3 = p2 - p1;
float result = p1.cross(p2);

移除class特性:

// 类变成结构体
typedef struct {float x, y;
} Point2f;// 重载运算符变成传参函数
Point2f Point2f_sub(const Point2f& self, const Point2f& other) {auto x = self.x, y = self.y;return Point2f{x - other.x, y - other.y};
}// 类方法变成传参函数
float Point2f_cross(const Point2f& self, const Point2f& other) {auto x = self.x, y = self.y;return x * other.y - y * other.x;
}// 调用
Point2f p1, p2;
Point2f p3 = Point2f_sub(p2, p1);
float result = Point2f_cross(p1, p2);

2.3 移除vector特性

vector 是动态数组,必然不可能重构成支持弹性数组的结构、同时还能支持 [] 符号按下标取出元素。因为弹性数组必然导致内存重新分配,那么首地址就会改变。而C语言中 [] 符号只是地址求值的语法糖,肯定会取出错误的结果。

完全等效的做法我可以实现一个 vector 结构体,里面包含数据长度(length)和数据区(data)两个属性。在 push_back 的时候实现内存重新分配,并对 length 和 data 重新赋值。在取元素的时候从 vector->data 位置取出数据,这样就可以实现内存重新分配也不会影响数据区的取值。

但是这样过于复杂,对源代码改动量也过大(求值部分需要依次增加 ->data),我不如评估一个数组的最大长度,然后将数组首地址数组长度的指针一起传入函数,在 vector 需要变化长度(例如 push_back)的时候,同步修改这个长度数据。

C++代码:

int foo(std::vector<Point2f> &points)
{if(!points.empty())points.resize(4);points.push_back(Point2f(xi, yi));int N = points.size();std::vector<float> distPt(N);float* pDist = distPt.data();
}// 调用
std::vector<Point2f> points;
foo(points);
printf("Size=%d\n", points.size());

移除vector依赖:

int foo(Point2f* points, int* points_size)
{if (points_size[0])points_size[0] = 4;points[points_size[0]++] = Point2f(xi, yi);int N = points_size[0];float distPt[N];float* pDist = distPt;
}// 调用
Point2f points[100];  // 申请足够大的空间
int points_size = 0;
foo(points, &points_size);
printf("Size=%d\n", points_size);

需要注意的是,*array_size++array_size[0]++ 的计算不等效,注意运算符的计算顺序。

另外,如果vector传入函数之后并未发生长度改变(例如没有 push_back),那么数据长度可以不用传入数字指针类型,直接传入数字本身也行。

2.4 移除std函数

需要注意 std::abs 对应的不是 abs 函数,而是 fabs,否则返回值会成为整数。maxmin 同理。

C++C
#include <iostream>#include <stdio.h>
std::cout << "Name: " << name << std::endl;printf(“Name: %s\n”, name);
std::cout << "Age: " << age << std::endl;printf(“Age: %d\n”, age);
#include <algorithm>#include <math.h>
std::absfabs
std::minfmin
std::maxfmax
std::ininfininf
std::isnanisnan
std::swapTYPE temp = a; a = b; b = temp;

2.5 移除引用传值

C++为了减少拷贝、和提高语法的可读性,引入了 &parameter 这样的引用传参形式。

在C语言中和传入 &parameter 完全等效的写法,是传入指针,然后在函数内求地址。

但是如果传入的是 const &parameter 类型,说明 &parameter 在函数内没有被更改过,所以直接把 & 去掉就可以了(也许C++这么写的考虑是减少内存拷贝次数?我不太清楚)。

2.6 移除auto类型推导

C语言中的 auto 关键字表示的实际上是 int,需要将C++中所有出现 auto 的地方替换为真正需要的类型。

至于怎么类型推导,那当然是人肉推导啦(笑)。

2.7 C++中的关键词

C++中比C语言多几个关键词,可以用宏定义来替代:

#define bool char
#define true 1
#define false 0
#define nullptr 0

2.8 其他语言差异

C++代码:

return Point2f(x, y);

移植为C语言:

Point2f result = {x, y};
return result;  // 无法直接赋值返回

3、测试和杂项

3.1 C语言和C++编译兼容

要在C++中调用C语言编译文件中的程序,需要在头文件中增加一段:

#ifdef __cplusplus
extern "C" {
#endif// 函数原型定义
int foo(int arg1, float arg2, char* arg3);#ifdef __cplusplus
}
#endif

3.2 GCC用C语言规则编译源文件

使用 -x 参数可以指定按照特定语言规则编译某种文件,多次指定 -x 参数后则可混合规则编译:

g++ -xc source.cpp -xc++ test.cpp -o test.exe

此命令可将 source.cpp 文件依照C语言规则编译,test.cpp 依照C++规则编译,最后通过C++规则进行链接,生成为 test.exe 文件。

3.3 修改文件名后缀

.cpp 后缀的文件修改为 .c 后缀即可,可避免某些编译器只能通过文件名后缀识别代码语言。

4、最终代码

最终结果用约400行C语言代码完成。

代码仓库可见GitHub:

https://github.com/znsoooo/opencv-calc-rotated-iou

分步修改步骤可见提交记录:

https://github.com/znsoooo/opencv-calc-rotated-iou/commits/master

这篇关于将OpenCV的代码从C++移植到C语言过程小记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1087026

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n