天天看點

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,這樣就不會互相幹擾了