opencv-SparseMat稀疏矩阵
OpenCV中一般一张图片在内存中用Mat来表述及管理,Mat内部申请一块类似与数组的内存用于存储图片中的每个像素的值即为稠密矩阵,但是有时在矩阵中其值为零的元素远远多于非为零的元素个数即稀疏矩阵,如何此时还使用Mat进行存储 显然非常浪费空间,为了应对此中场景,OpenCV使用SparseMat类来应对稀疏矩阵场景,稀疏矩阵内部内存为一个hash表,其值为0的元素其实并没有占用内存空间,只存储其值为非零的元素,值为0的元素不占用内存空间,同时为了保证查找速度快 内部使用一个hash表进程存储
数据访问
指针方式
cv::SparseMat sm9; //空构造 int sz[2]={9,5}; cv::SparseMat sm1(2, sz, CV_8U); //赋值构造 //参数1:维度 //参数2:各维大小 注意是数组 //参数3:数据类型 cv::SparseMat sm(sm1); //拷贝构造 //这里的sm0可以是SparseMat或者Mat int number = 0; for (int i = 0; i < 9; i++) { for (int j = 0; j < 5; j++) { *sm1.ptr(i, j, 1) = number; //指定位置赋值 //参数1和参数2:行和列 //参数3: 如果指定位置的元素没有申请内存,是否申请内存;1:申请内存,0:不申请内存 number++; } } int k; for (int i = 0; i < 9; i++) { for (int j = 0; j < 5; j++) { k=*sm1.ptr(i, j, 1); //返回指定位置的值 printf("id0: %d, id1:%d, value:%d\n",i,j, k); //参数1和参数2:行和列 //参数3: 如果指定位置的元素没有申请内存,是否申请内存;1:申请内存,0:不申请内存 number++; } }
int sz[2]={9,5}; cv::SparseMat sm1(2, sz, CV_32F); int number = 0; for (int i = 0; i < 9; i++) { for (int j = 0; j < 5; j++) { *(float *)sm1.ptr(i, j, 1) = number*3.14; //指定位置赋值 //参数1和参数2:行和列 //参数3: 如果id0元素没有申请内存,是否申请内存;1:申请内存,0:不申请内存 number++; } } float k; for (int i = 0; i < 9; i++) { for (int j = 0; j < 5; j++) { k=*(float *)sm1.ptr(i, j, 1); //返回指定位置的值 printf("id0: %d, id1:%d, value:%f\n",i,j, k); //参数1和参数2:行和列 //参数3: 如果id0元素没有申请内存,是否申请内存;1:申请内存,0:不申请内存 number++; } }
ref读写引用方式
int size[2] = { 6, 5 }; int number = 0; cv::SparseMat sm1(2, size, CV_32F); for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { sm1.ref<float>(i,j) = number*3.14; //给指定位置元素的第一通道赋值 number++; } } float k; for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { k=sm1.ref<float>(i, j); //返回指定位置元素第一通道值 printf("id0: %d, id1:%d, value:%f\n",i,j, k); } }
cv::Mat M(6, 5, CV_8UC3,cv::Scalar(0, 20, 30)); std::cerr<<M<<std::endl; cv::SparseMat sm(M); sm.ref<int>(1,2) = 50+55*256+44*256*256;//给指定像素指定通道赋值 //50 是第一通道的值 //55*256 是第二通道的值55 数据类型是8位所以256 //44*256*256 是第三通道的值44 int v=sm.ref<int>(1,2);//返回指定像素的值(包含所有通道) //2897714 int k=(v&0xFF); //得到第一通道值 int k1=(v&0xFF00)/256; //得到第二通道值 int k2=(v&0xFF0000)/(256*256); //得到第三通道值 std::cerr<<v<<std::endl; std::cerr<<"k="<<k<<","<<"k1="<<k1<<","<<"k2="<<k2<<std::endl; cv::Mat M1; sm.copyTo(M1); std::cerr<<M1<<std::endl;
find读取元素方式
find方式只可读,不可写,返回为一个指针常量,find也是一个模板函数,支持常用的数据类型,如果该元素不存在,返回NULL
int size[2] = { 6, 5 }; int number = 0; cv::SparseMat sm1(2, size, CV_32F); for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { sm1.ref<float>(i,j) = number*3.14; number++; } } float k; for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { k=*sm1.find<float>(i, j); //返回指定位置元素值 printf("id0: %d, id1:%d, value:%f\n",i,j, k); } }
value读取元素方式
也是只可读不可写,与find不同的是,函数直接返回的是元素的值而不是指针
int size[2] = { 6, 5 }; int number = 0; cv::SparseMat sm1(2, size, CV_32F); for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { sm1.ref<float>(i,j) = number*3.14; number++; } } float k; for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { k=sm1.value<float>(i, j); //返回指定位置元素值 printf("id0: %d, id1:%d, value:%f\n",i,j, k); } }
迭代访问
int size[2] = { 6, 5 }; int number = 0; cv::SparseMat sm1(2, size, CV_32F); for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { sm1.ref<float>(i,j) = number*3.14; number++; } } cv::SparseMatIterator_<float> it = sm1.begin<float>(); //迭代器开始位置 cv::SparseMatIterator_<float> it_end = sm1.end<float>();//迭代器结束位置 for (; it != it_end; ++it) { const cv::SparseMat::Node * node = it.node();//返回当前迭代器的节点 printf("id0: %d, id1:%d, value:%f\n", node->idx[0], node->idx[1], sm1.value<float>(it.node())); //node->idx[0] 返回行号 //node->idx[1] 返回列号 //sm1.value<float>(it.node()) 返回元素值 }
可以看到节点的存储并不是按照矩阵的索引进行排序
int size[2] = { 6, 5 }; int number = 0; cv::SparseMat sm1(2, size, CV_32F); for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { sm1.ref<float>(i,j) = number*3.14; number++; } } //sm1.clear(); //一次性清除所有hash表,将所有元素值清为0,并释放所有hash表内存 int cout=sm1.nzcount(); //返回sm1中非零元素的数目 sm1.erase(0, 1); //移除指定位置处的元素;元素值变为0 cout=sm1.nzcount(); size_t h = sm1.hash( 1, 2 );//返回指定位置处的哈希值 std::cout <<cout << std::endl; std::cout <<h << std::endl; float k; for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { k=sm1.value<float>(i, j); printf("id0: %d, id1:%d, value:%f\n",i,j, k); } }
获取函数
int size[2] = { 6, 5 }; int number = 0; cv::SparseMat sm(2, size, CV_32F); for (int i = 0; i < 6; i++) { for (int j = 0; j < 5; j++) { sm.ref<float>(i,j) = number*3.14; number++; } } int t=sm.type(); //获取矩阵的数据类型 /* 返回值对应的数据类型 #define CV_8U 0 #define CV_8S 1 #define CV_16U 2 #define CV_16S 3 #define CV_32S 4 #define CV_32F 5 #define CV_64F 6 #define CV_USRTYPE1 7 */ int d=sm.depth();//获取矩阵元素深度,其返回的其类型和Mat一致 /* 返回值对应的元素深度 CV_8U = 0 - 8-bit unsigned integers ( 0..255 ) CV_8S = 1 - 8-bit signed integers ( -128..127 ) CV_16U = 2 - 16-bit unsigned integers ( 0..65535 ) CV_16S = 3 - 16-bit signed integers ( -32768..32767 ) CV_32S = 4 - 32-bit signed integers ( -2147483648..2147483647 ) CV_32F = 5 - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN ) CV_64F = 6 - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN ) CV_USRTYPE1=7 */ int c=sm.channels(); //返回矩阵通道数目 const int* s=sm.size(); //返回矩阵大小,其返回值为int *,相当于数组,数组大小与维度相同,如果矩阵还未定义大小或还未申请内存则返回NULL int sx=s[0]; //矩阵行数 int sy=s[1]; //矩阵列数 int dim=sm.dims(); //返回矩阵维度 int nz=sm.nzcount();//返回矩阵中非零像素个数,相当于hash table的节点数 int e=sm.elemSize();//返回每个元素占用的字节数大小(带通道) int e1=sm.elemSize1();//返回单个元素值占用的字节数大小 std::cerr<<e1<<std::endl;
SparseMat与Mat相互转换
cv::Mat M(6, 5, CV_8UC3,cv::Scalar(0, 20, 40)); std::cerr<<M<<std::endl; cv::SparseMat sm(M); //根据Mat创建SparseMat矩阵 sm.ref<int>(1,2) = 75+55*256;//给指定像素指定通道赋值 //75 是第一通道的值 //55*256 是第二通道的值55 cv::Mat M1,M2; sm.copyTo(M1); //把稀松矩阵sm的数据拷贝给Mat矩阵M1 sm.convertTo(M2, CV_32F,1.0/2,4);//把稀松矩阵sm的数据拷贝给Mat矩阵M2 /* 参数2:目标矩阵的数据类型 参数3:比例因子:源矩阵的数据乘以比例因子=目标矩阵数据 参数4:加的值:目标矩阵数据=源矩阵的数据*比例因子+参数4 */ cv::SparseMat sm1; sm.convertTo(sm1, CV_8U);//把稀松矩阵sm的数据拷贝给sm1 std::cerr<<M2<<std::endl;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)