天天看点

opencv元素访问基础知识图像基础知识和opencv踩的坑

图像基础知识和opencv踩的坑

0是黑,255是白。灰度图和彩色图区别为:组成不同、通道不同、表示不同。 一、组成不同 1、灰度图:灰度图把白色与黑色之间按对数关系用灰度表示的图像

深度图16为都要进行归一化处理

opencv的坑

opencv访问mat元素的时候必须用相同的类型

for(int i=0;i<nr;i++)
    {
        for(int j=0;j<nc;j++)
        {
            if(image.at<uchar>(i,j)==0)
            {
                image.at<uchar>(i,j)=255;
            }
        }
    }
           

这是cv_8u1; 如果我用ushort访问那么会出现下面的错误;还有就是切记要注意矩阵范围是uchar还是ushort

Segmentation fault (core dumped)
           

掩膜

inpaintMask:掩码矩阵,为0的像素作修复处理,不为0的不作修复处理

掩膜只对0像素进行处理

用选定的图像、图形或物体,对待处理的图像(全部或局部)进行遮挡,来控制图像处理的区域或处理过程。用于覆盖的特定图像或物体称为掩模或模板

关于cv::mat at进行访问的问题

row == height == Point.y
col == width  == Point.x
           
opencv元素访问基础知识图像基础知识和opencv踩的坑

这是因为在计算机中,图像是以矩阵的形式保存的。一张宽度640像素、长度480像素的灰度图保存在一个480 * 640的矩阵中。

先行后列。而我们习惯的坐标表示是先X横坐标,再Y纵坐标。在OpenCV中需要对矩阵进行计算,先行再列。这种隐形的错误需要细心。

Rect的构造函数给定的X,Y坐标即为矩形的左上角坐标。

设置为(0,100),效果如下。

opencv元素访问基础知识图像基础知识和opencv踩的坑
1.坐标体系中的零点坐标为图片的左上角,X轴为图像矩形的上面那条水平线;Y轴为图像矩形左边的那条垂直线。该坐标体系在诸如结构体Mat,Rect,Point中都是适用的。(虽然网上有学着说OpenCV中有些数据结构的坐标原点是在图片的左下角,但是我暂时还没碰到过)。

2.在使用image.at(x1, x2)来访问图像中点的值的时候,x1并不是图片中对应点的x轴坐标,而是图片中对应点的y坐标。因此其访问的结果其实是访问image图像中的Point(x2, x1)点,即与image.at(Point(x2, x1))效果相同。

3.如果所画图像是多通道的,比如说image图像的通道数时n,则使用Mat::at(x, y)时,其x的范围依旧是0到image的height,而y的取值范围则是0到image的width乘以n,因为这个时候是有n个通道,所以每个像素需要占有n列。但是如果在同样的情况下,使用Mat::at(point)来访问的话,则这时候可以不用考虑通道的个数,因为你要赋值给获取Mat::at(point)的值时,都不是一个数字,而是一个对应的n维向量。
           

也就是说,用point(i,j)访问的就是正常的(i,j)这个点,而at访问的是(j,i)这个点

opencv 元素访问

opencv进行at访问的时候,记住数据占据空间的大小是不一样的, 比如,ushort是 16位的占据两个字节, double64位会占据8个字节

如果at 访问的ushort数据的时候,用double那么一次性读取的就是8个字节的数据,就会出现数据转换时候的错误,同样,原来是double的数据如果强制转换成ushort在放进存放这个double的地方,就相当于原来16位的来存8位,原来的16位中剩下的8位会因为没有被释放掉造成内存泄露。

opencv 各种访问速度

at ptr data

这三种访问速度都很接近

at: 31.06
ptr: 31.25
data: 31.3
           

测试代码

#include <time.h>

#include <iostream>
#include <opencv2/opencv.hpp>

#include "common_op.h"

int main() {
  clock_t start, stop;
  double duration;
  double k = 0;
  cv::Mat database = cv::imread("mean_depth.png", cv::IMREAD_ANYDEPTH);
  ushort tmp;

  start = clock();
  for (int j = 0; j < 100; j++) {
    while (k != 1000000) {
      k++;
      for (int i = 0; i < database.rows; i++) {
        for (int j = 0; j < database.cols; j++) {
          tmp = database.at<ushort>(i, j);
        }
      }
    }
  }
  stop = clock();
  duration = static_cast<double>(stop - start) / 100.0;
  std::cout << RED;
  std::cout << "at: " << duration << std::endl;

  k = 0;
  start = clock();
  for (int j = 0; j < 100; j++) {
    while (k != 1000000) {
      k++;
      for (int i = 0; i < database.rows; i++) {
        ushort *ptr_data = database.ptr<ushort>(i);
        for (int j = 0; j < database.cols; j++) {
          tmp = ptr_data[j];
        }
      }
    }
  }
  stop = clock();
  duration = static_cast<double>(stop - start) / 100;
  std::cout << RED;
  std::cout << "ptr: " << duration << std::endl;

  k = 0;
  start = clock();
  for (int j = 0; j < 100; j++) {
    while (k != 1000000) {
      k++;
      for (int i = 0; i < database.rows; i++) {
        ushort *ptr_data = database.ptr<ushort>(i);
        for (int j = 0; j < database.cols; j++) {
          tmp = *(database.data + database.step[0] * i + database.step[1] * j);
        }
      }
    }
  }
  stop = clock();
  duration = static_cast<double>(stop - start) / 100;
  std::cout << RED;
  std::cout << "data: " << duration << std::endl;
}
           

tip:

无论用什么方法访问其实都是根据空间的取法来的

举例子:

at :

at 根据后面的类型来进行取值 at<ushort> 这时候ushort是16位的,所以每次取数据就是16位的。如果这个时候取值空间的类型不是ushort而是别的比如double那么取出16位而double是64位这时候数值就会发生变化
ptr也一样,根据ptr的类型来进行取值,来取多少位
data一样
           

关于opencv与stl 的操作

stl中的push_back

对于内建类型(int float char等),容器的工作方式是纯粹的位拷贝,这里没有什么需要多说的。

对于自定义的对象,容器容纳了对象(比如通过insert或push_back等),但容器中存放的对象不是你给它们的那个对象,因为两个对象在内存中的位置不一样。此外,当你从容器中获取一个对象时,你所得到的对象不是容器里的那个对象。取而代之的是,当你向容器中添加一个对象(比如通过insert或push_back等),进入容器的是你指定的对象的拷贝。拷进去,拷出来。拷贝是STL的方式。
           
明白了容器的工作方式,那么进一步来讨论容器存放对象和指针在操作过程中的开销。内建类型的数据进行拷贝的方式是位拷贝,自定义类型的数据进行拷贝会调用类的拷贝构造函数,这个函数是每个类都有的,如果类中没有显式的声明那么编译器也会给提供一个默认的拷贝构造函数。如果一个类的数据非常多,或者包含其他复杂自定义类型,那么此时类的拷贝构造的开销是非常大的。
           

关于opencv的mat

Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。因此,当在程序中传递图像并创建拷贝时,大的开销是由矩阵造成的,而不是信息头。所以,除非有必要,否则我们不应该拷贝大的图像,因为这会降低程序速度。
为了搞定这个问题,OpenCV使用引用计数机制。其思路是让每个 Mat 对象有自己的信息头,但共享同一个矩阵。这通过让矩阵指针指向同一地址而实现。而拷贝构造函数则 只拷贝信息头和矩阵指针 ,而不拷贝矩阵。但某些时候你仍会想拷贝矩阵本身(不只是信息头和矩阵指针),==这时可以使用函数 clone() 或者 copyTo() 。==
           

所以,如果vector<mat> 这个时候如果 push_back(mat_a) 这时候调用mat的拷贝构造函数,此时,mat_a只会只拷贝信息头和矩阵指针 ,而不拷贝矩阵。且引用机制加一,这时候如果改变mat_a里面的内容,那么就会同时改变vector的存放的值,而且局部mat_a析构的时候只是引用计数减去一 但内容还在

为了避免这种情况:

改写: push_back(mat_a.clone()) 相当于放进去了一个匿名的对象,这时候就可以隔离vector和mat_a,这样就不会互相干扰了