opencv笔记(二十四)——图像遍历的4种方式(at、指针、isContinuous、迭代器)、在Vector尾部加数据函数push_back()。

一、遍历图像

    我们在实际应用中对图像进行的操作,往往并不是将图像作为一个整体进行操作,而是对图像中的所有点或特殊点进行运算,所以遍历图像就显得很重要,如何高效的遍历图像是一个很值得探讨的问题。

1.1 at<typename>(i,j)

        Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。下面我们通过一个图像处理中的实际来说明它的用法。

        在实际应用中,我们很多时候需要对图像降色彩,因为256*256*256实在太多了,在图像颜色聚类或彩色直方图时,我们需要用一些代表性的颜色代替丰富的色彩空间,我们的思路是将每个通道的256种颜色用64种代替,即将原来256种颜色划分64个颜色段,每个颜色段取中间的颜色值作为代表色。

 

  void colorReduce(Mat& image,int div)
  {
      for(int i=0;i<image.rows;i++)//遍历所有像素
      {
          for(int j=0;j<image.cols;j++)
          {
            if(image.channels() == 1)//判断是否是单通道
             {
               image.at<uchar>(i,j) = image.at<uchar>(i,j)/div*div+div/2;//读出image第i行第j列的像素值
             }
            else if(image.channels() == 3)//判断是否是RGB三通道
            {
              image.at<Vec3b>(i,j)[0]=image.at<Vec3b>(i,j)[0]/div*div+div/2;//三通道
              image.at<Vec3b>(i,j)[1]=image.at<Vec3b>(i,j)[1]/div*div+div/2;
              image.at<Vec3b>(i,j)[2]=image.at<Vec3b>(i,j)[2]/div*div+div/2;
            }
         }
     }
 }

 

image

通过上面的例子我们可以看出,at方法取图像中的点的用法:

image.at<uchar>(i,j):取出灰度图像中i行j列的点

image.at<Vec3b>(i,j)[k]:取出彩色图像中i行j列第k通道的颜色点。其中uchar,Vec3b都是图像像素值的类型,不要对Vec3b这种类型感觉害怕,其实在core里它是通过typedef Vec<T,N>来定义的,N代表元素的个数,T代表类型。

更简单一些的方法:OpenCV定义了一个Mat的模板子类为Mat_,它重载了operator()让我们可以更方便的取图像上的点。

Mat_<uchar> im=image;

im(i,j)=im(i,j)/div*div+div/2;

 
#include<opencv2\opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
 
int main()
{
	Mat grayim(600, 800, CV_8UC1);
	Mat colorim(600, 800, CV_8UC3);
	//遍历所有像素,并设置像素值
	for (int i = 0; i < colorim.rows; i++)
	for (int j = 0; j < colorim.cols;j++)
	{
		Vec3b pixel;//创建数组pixel
		pixel[0] = i % 255;
		pixel[1] = j % 255;
		pixel[2] = 0;

            /*以下内容读者可忽略
        image.at<cv::Vec3b>(j, i)[0] = 255;
        image.at<cv::Vec3b>(j, i)[1] = 255;
        image.at<cv::Vec3b>(j, i)[2] = 255;椒盐噪点那块*/

		//将第i行j列的像素值设为像素值为pixel的颜色
		colorim.at<Vec3b>(i, j) = pixel;
		//将i行j列的像素值设为128
		grayim.at<uchar>(i, j) = (i+j)%255;
	}
	imshow("colcorim", colorim);
	imshow("grayim", grayim);
	waitKey(20000);
	return 0;
}

生成的图像如图,so beautiful

需要注意的是,如果遍历图像并不推荐使用 需要注意的是,如果遍历图像并不推荐使用 at() 函数。使用at()函数优点是代码可读性高,但效率不高。

1.2、高效一点:用指针来遍历图像

上面的例程中可以看到,我们实际喜欢把原图传进函数内,但是在函数内我们对原图像进行了修改,而将原图作为一个结果输出,很多时候我们需要保留原图,这样我们需要一个原图的副本。

void colorReduce(const Mat& image,Mat& outImage,int div)
 {
     // 创建与原图像等尺寸的图像
     outImage.create(image.size(),image.type());
     int nr=image.rows;
     // 将3通道转换为1通道
     int nl=image.cols*image.channels();
    for(int k=0;k<nr;k++)
     {
         // 每一行图像的指针
         const uchar* inData=image.ptr<uchar>(k);
         uchar* outData=outImage.ptr<uchar>(k);
         for(int i=0;i<nl;i++)
         {
             outData[i]=inData[i]/div*div+div/2;
         }
     }
 }

从上面的例子中可以看出,取出图像中第i行数据的指针:image.ptr<uchar>(i)

        值得说明的是:程序中将三通道的数据转换为1通道,在建立在每一行数据元素之间在内存里是连续存储的每个像素三通道像素按顺序存储。也就是一幅图像数据最开始的三个值,是最左上角的那像素的三个通道的值。

        但是这种用法不能用在行与行之间,因为图像在OpenCV里的存储机制问题行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。但是我们可以申明一个连续的空间来存储图像,这个话题引入下面最为高效的遍历图像的机制。

1.3、更高效的方法

上面已经提到过了,一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行。

 void colorReduce(const Mat& image,Mat& outImage,int div)
 {
     int nr=image.rows;
     int nc=image.cols;
     outImage.create(image.size(),image.type());
     if(image.isContinuous()&&outImage.isContinuous())
     {
         nr=1;
         nc=nc*image.rows*image.channels();
     }
     for(int i=0;i<nr;i++)
    {
         const uchar* inData=image.ptr<uchar>(i);
         uchar* outData=outImage.ptr<uchar>(i);
         for(int j=0;j<nc;j++)
        {
             *outData++=*inData++/div*div+div/2;
         }
     }
 }

用指针除了用上面的方法外,还可以用指针来索引固定位置的像素:

image.step返回图像一行像素元素的个数(包括空白元素),image.elemSize()返回一个图像像素的大小。

&image.at<uchar>(i,j)=image.data+i*image.step+j*image.elemSize();

1.4、还有吗?用迭代器来遍历。

下面的方法可以让我们来为图像中的像素声明一个迭代器:

MatIterator_<Vec3b> it;

Mat_<Vec3b>::iterator it;

如果迭代器指向一个const图像,则可以用下面的声明:

MatConstIterator<Vec3b> it; 或者

Mat_<Vec3b>::const_iterator it;

下面我们用迭代器来简化上面的colorReduce程序:

 void colorReduce(const Mat& image,Mat& outImage,int div)
 {
     outImage.create(image.size(),image.type());
     MatConstIterator_<Vec3b> it_in=image.begin<Vec3b>();
     MatConstIterator_<Vec3b> itend_in=image.end<Vec3b>();
     MatIterator_<Vec3b> it_out=outImage.begin<Vec3b>();
     MatIterator_<Vec3b> itend_out=outImage.end<Vec3b>();
     while(it_in!=itend_in)
     {
         (*it_out)[0]=(*it_in)[0]/div*div+div/2;
         (*it_out)[1]=(*it_in)[1]/div*div+div/2;
         (*it_out)[2]=(*it_in)[2]/div*div+div/2;
         it_in++;
         it_out++;
     }
 }

如果你想从第二行开始,则可以从image.begin<Vec3b>()+image.rows开始。

上面4种方法中,第3种方法的效率最高!

1.5、图像的邻域操作

很多时候,我们对图像处理时,要考虑它的邻域,比如3*3是我们常用的,这在图像滤波、去噪中最为常见,下面我们介绍如果在一次图像遍历过程中进行邻域的运算。

下面我们进行一个简单的滤波操作,滤波算子为[0 –1 0;-1 5 –1;0 –1 0]。

它可以让图像变得尖锐,而边缘更加突出。核心公式即:sharp(i.j)=5*image(i,j)-image(i-1,j)-image(i+1,j

)-image(i,j-1)-image(i,j+1)。

 void ImgFilter2d(const Mat &image,Mat& result)
 {
     result.create(image.size(),image.type());
     int nr=image.rows;
     int nc=image.cols*image.channels();
     for(int i=1;i<nr-1;i++)
     {
         const uchar* up_line=image.ptr<uchar>(i-1);//指向上一行
        const uchar* mid_line=image.ptr<uchar>(i);//当前行
        const uchar* down_line=image.ptr<uchar>(i+1);//下一行
         uchar* cur_line=result.ptr<uchar>(i);
         for(int j=1;j<nc-1;j++)
         {
             cur_line[j]=saturate_cast<uchar>(5*mid_line[j]-mid_line[j-1]-mid_line[j+1]-
                 up_line[j]-down_line[j]);
         }
     }
     // 把图像边缘像素设置为0
     result.row(0).setTo(Scalar(0));
     result.row(result.rows-1).setTo(Scalar(0));
     result.col(0).setTo(Scalar(0));
     result.col(result.cols-1).setTo(Scalar(0));
 }

image

上面的程序有以下几点需要说明:

1,staturate_cast<typename>是一个类型转换函数,程序里是为了确保运算结果还在uchar范围内。

2,row和col方法返回图像中的某些行或列,返回值是一个Mat。

3,setTo方法将Mat对像中的点设置为一个值,Scalar(n)为一个灰度值,Scalar(a,b,c)为一个彩色值。

1.6、图像的算术运算

Mat类把很多算数操作符都进行了重载,让它们来符合矩阵的一些运算,如果+、-、点乘等。

下面我们来看看用位操作和基本算术运算来完成本文中的colorReduce程序,它更简单,更高效。

将256种灰度阶降到64位其实是抛弃了二进制最后面的4位,所以我们可以用位操作来做这一步处理。

首先我们计算2^8降到2^n中的n:int n=static_cast<int>(log(static_cast<double>(div))/log(2.0));

然后可以得到mask,mask=0xFF<<n;

用下面简直的语句就可以得到我们想要的结果:

result=(image&Scalar(mask,mask,mask))+Scalar(div/2,div/2,div/2);

很多时候我们需要对图像的一个通信单独进行操作,比如在HSV色彩模式下,我们就经常把3个通道分开考虑。

1 vector<Mat> planes;
2 // 将image分为三个通道图像存储在planes中
3 split(image,planes);
4 planes[0]+=image2;
5 // 将planes中三幅图像合为一个三通道图像
6 merge(planes,result);

 

二、Vector尾部加数据——Mat::push_back

Adds elements to the bottom of the matrix.

C++: template<typename T> void Mat::push_back(const T& elem)

C++: void Mat::push_back(const Mat& m)

Parameters:
  • elem – Added element(s).
  • m – Added line(s).

        The methods add one or more elements to the bottom of the matrix. They emulate the corresponding method of the STL vector class. When elem is Mat , its type and the number of columns must be the same as in the container matrix.

        在为mat增加一行的时候,用到push_back。首先用vector存要加的数据,用push_back加入,编译没有问题,运行时抛出sigsegv错误。

        我理解按照上述push_back的定义,他是可以接受vector的参数,并且由于编译没有问题,也更加使我相信这一点。可惜!

使用方法:

        先创建一个Mat(1, 16, CV_64F), 利用at()函数为其赋值。最后使用push_back加入。

 Mat temp(1, 16, CV_64F);;
 for (int j = 0; j < 10; j++)
 {
     temp.at<double>(0, j-1) = j;
 }
 origin.push_back(temp);
 

https://blog.csdn.net/daoqinglin/article/details/23628125

https://blog.csdn.net/xuhang0910/article/details/47058419

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是将该段代码转换为C++ DLL函数的实现,并在最后将C++返回给C#的示例代码: 首先,需要在C++中引入相关头文件和命名空间: ```c++ #include <vector> #include <string> #include <fstream> #include <iostream> #include <algorithm> #include <numeric> #include <chrono> #include <memory> #include <stdexcept> #include <cstdlib> #include <cstring> #include <cassert> #include <cmath> #include <onnxruntime_cxx_api.h> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; using namespace onnxruntime; ``` 然后,实现C++ DLL函数: ```c++ // 将Mat对象复制到float数组 void copy_from_mat(Mat& img, float* target) { vector<Mat> temp; split(img, temp); int wl = img.cols * img.rows; for (int i = 0; i < temp.size(); i++) { uchar* rawDataPointer = temp[i].data; memcpy(target + i * wl, rawDataPointer, wl * sizeof(float)); } } // 将C++返回给C#的结果转换为float数组 void results_to_float_array(OrtValue& result, float* c) { auto tensor = result.GetTensor<float>(); auto tensor_shape = tensor.Shape(); int num_elements = tensor_shape.Size(); memcpy(c, tensor.Data(), num_elements * sizeof(float)); } // 实现C++ DLL函数 extern "C" __declspec(dllexport) int run_session(float* img_data, int img_width, int img_height, char* model_path, float* c) { try { // 初始化InferenceSession SessionOptions session_options; session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); session_options.SetExecutionMode(ExecutionMode::ORT_SEQUENTIAL); session_options.SetIntraOpNumThreads(1); session_options.SetInterOpNumThreads(1); session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED); InferenceSession session(session_options); OrtMemoryInfo info("Cpu", OrtDeviceAllocator, 0, OrtMemTypeCPU); // 载模型 session.LoadModel(model_path); // 将输入数据复制到DenseTensor float* typedArr = new float[3 * img_width * img_height]; copy_from_mat(img, typedArr); vector<int64_t> dims = {1, 3, img_height, img_width}; auto input = OrtValue::CreateTensor<float>(info, typedArr, num_elements, dims.data(), dims.size()); delete[] typedArr; // 执行推理 vector<OrtValue> ort_outputs = session.Run({ {session.GetInputName(0), input} }); // 将输出结果转换为float数组 results_to_float_array(ort_outputs[0], c); return 0; } catch (const exception& ex) { cerr << ex.what() << endl; return -1; } } ``` 最后,需要在C#中声明C++ DLL函数,并调用该函数: ```c# [DllImport("your_dll_name.dll")] public static extern int run_session(float[] img_data, int img_width, int img_height, string model_path, float[] c); // 调用C++ DLL函数 float[] img_data = ...; // 输入图像数据 int img_width = ...; // 输入图像宽度 int img_height = ...; // 输入图像高度 string model_path = ...; // 模型路径 float[] c = new float[...]; // 存储输出结果的数组 int ret = run_session(img_data, img_width, img_height, model_path, c); if (ret == 0) { // 输出结果处理代码 } else { // 处理错误 } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值