《視覺SLAM十四講》第五講超級詳細解讀-imageBasics.cpp

一、爲什麼圖像是波?

我們知道,圖像由像素組成。下圖是一張 400 x 400 的圖片,一共包含了 16 萬個像素點。
在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述
每個像素的顏色,可以用紅、綠、藍、透明度四個值描述,大小範圍都是0 ~ 255,
比如黑色是[0, 0, 0, 255],白色是[255, 255, 255, 255]。
如果把每一行所有像素(上例是400個)的紅、綠、藍的值,依次畫成三條曲線,就得到了下面的圖形。
在這裏插入圖片描述
可以看到,每條曲線都在不停的上下波動。有些區域的波動比較小,有些區域突然出現了大幅波動(比如 54 和 324 這兩點)。
對比一下圖像就能發現,曲線波動較大的地方,也是圖像出現突變的地方。
在這裏插入圖片描述
這說明波動與圖像是緊密關聯的。圖像本質上就是各種色彩波的疊加。

二、頻率

綜上所述,圖像就是色彩的波動:波動大,就是色彩急劇變化;
波動小,就是色彩平滑過渡。因此,波的各種指標可以用來描述圖像。
頻率(frequency)是波動快慢的指標,單位時間內波動次數越多,頻率越高,反之越低。
在這裏插入圖片描述

上圖是函數sin(Θ)的圖形,在2π的週期內完成了一次波動,頻率就是1。
在這裏插入圖片描述

上圖是函數sin(2Θ)的圖形,在2π的週期內完成了兩次波動,頻率就是2。
所以,色彩劇烈變化的地方,就是圖像的高頻區域;色彩穩定平滑的地方,就是低頻區域。

三、濾波器

濾波器(filter):過濾掉某些波,保留另一些波,

低通濾波器(lowpass):減弱或阻隔高頻信號,保留低頻信號
高通濾波器(highpass):減弱或阻隔低頻信號,保留高頻信號

下面是低通濾波的例子。
在這裏插入圖片描述

上圖中,藍線是原始的波形,綠線是低通濾波lowpass後的波形。可以看到,綠線的波動比藍線小很多,非常平滑。

下面是高通濾波的例子。
在這裏插入圖片描述

上圖中,黃線是原始的波形,藍線是高通濾波highpass後的波形。
可以看到,黃線的三個波峯和兩個波谷(低頻波動),
在藍線上都消失了,而黃線上那些密集的小幅波動(高頻波動),則是全部被藍線保留。

再看一個例子。

在這裏插入圖片描述
上圖有三根曲線,黃線是高頻波動,紅線是低頻波動。它們可以合成爲一根曲線,就是綠線。
在這裏插入圖片描述
上圖中,綠線進行低通濾波和高通濾波後,得到兩根黑色的曲線,它們的波形跟原始的黃線和紅線是完全一致的。

四、圖像的濾波

瀏覽器實際上包含了濾波器的實現,因爲 Web Audio API 裏面定義了聲波的濾波。
這意味着可以通過瀏覽器,將lowpass和highpass運用於圖像。

lowpass使得圖像的高頻區域變成低頻,即色彩變化劇烈的區域變得平滑,也就是出現模糊效果。
在這裏插入圖片描述
在這裏插入圖片描述
上圖中,紅線是原始的色彩曲線,藍線是低通濾波後的曲線。

highpass正好相反,過濾了低頻,只保留那些變化最快速最劇烈的區域,也就是圖像裏面的物體邊緣,所以常用於邊緣識別。
在這裏插入圖片描述
在這裏插入圖片描述
上圖中,紅線是原始的色彩曲線,藍線是高通濾波後的曲線。

代碼詳細註釋

// imageBasics.cpp
#include <iostream>
#include <chrono>
using namespace std;

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

int main ( int argc, char** argv )
{
    // 讀取argv[1]指定的圖像
    cv::Mat image;
    // argv[1] = /home/q/projects/slambook/ch5/imageBasics/ubuntu.png
    image = cv::imread ( argv[1] ); //cv::imread函數讀取指定路徑下的圖像
    // 判斷圖像文件是否正確讀取
    if ( image.data == nullptr ) //數據不存在,可能是文件不存在
    {
        cerr<<"文件"<<argv[1]<<"不存在."<<endl;
        return 0;
    }
    
    // 文件順利讀取, 首先輸出一些基本信息
    cout<<"圖像寬爲"<<image.cols<<",高爲"<<image.rows<<",通道數爲"<<image.channels()<<endl;
    cv::imshow ( "image", image );      // 用cv::imshow顯示圖像
    cv::waitKey ( 0 );                  // 暫停程序,等待一個按鍵輸入
    // 判斷image的類型
    if ( image.type() != CV_8UC1 && image.type() != CV_8UC3 )
    {
        // 圖像類型不符合要求
        cout<<"請輸入一張彩色圖或灰度圖."<<endl;
        return 0;
    }

    // 遍歷圖像, 請注意以下遍歷方式亦可使用於隨機像素訪問
    // 使用 std::chrono 來給算法計時
    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
    for ( size_t y=0; y<image.rows; y++ )
    {
        // 用cv::Mat::ptr獲得圖像的行指針
        unsigned char* row_ptr = image.ptr<unsigned char> ( y );  // row_ptr是第y行的頭指針
        for ( size_t x=0; x<image.cols; x++ )
        {
            // 訪問位於 x,y 處的像素
            // 這裏比較不好理解,主要是每個像素是由多個通道值組成的,所以要乘上通道數
            unsigned char* data_ptr = &row_ptr[ x*image.channels() ]; // data_ptr 指向待訪問的像素數據
            // 輸出該像素的每個通道,如果是灰度圖就只有一個通道
            for ( int c = 0; c != image.channels(); c++ )
            {
                unsigned char data = data_ptr[c]; // data爲I(x,y)第c個通道的值
                cout << data << endl;
            }
        }
    }
    chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
    chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>( t2-t1 );
    cout<<"遍歷圖像用時:"<<time_used.count()<<" 秒。"<<endl;

    // 關於 cv::Mat 的拷貝
    // 直接賦值並不會拷貝數據
    cv::Mat image_another = image;
    // 修改 image_another 會導致 image 發生變化
    image_another ( cv::Rect ( 0,0,100,100 ) ).setTo ( 0 ); // 將左上角100*100的塊置零
    cv::imshow ( "image", image );
    cv::waitKey ( 0 );
    
    // 使用clone函數來拷貝數據
    cv::Mat image_clone = image.clone();
    image_clone ( cv::Rect ( 0,0,100,100 ) ).setTo ( 255 );
    cv::imshow ( "image", image );
    cv::imshow ( "image_clone", image_clone );
    cv::waitKey ( 0 );

    // 對於圖像還有很多基本的操作,如剪切,旋轉,縮放等,限於篇幅就不一一介紹了,請參看OpenCV官方文檔查詢每個函數的調用方法.
    cv::destroyAllWindows();
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章