OpenCV 基本操作

創(chuàng)建 cv::Mat

OpenCV 中用數(shù)據(jù)格式 cv::Mat 存儲圖片。

有以下幾種生成 cv::Mat 的方式:

直接或間接指定圖片的行、列維度
cv::Mat myMat(240, 320, CV_8U, cv::Scalar(255)); // 240 行 × 320 列,每個元素的類型為 8 位無符號整型,單通道,元素初值為 255

// 也可以將 Scalar(255) 簡寫成 255,但是注意 Scalar(0) 不能直接寫成 0 
cv::Mat myMat(240, 320, CV_8U, 255); 

// 圖片大小也可以用 Size() 的格式送入
cv::Mat myMat(cv::Size(320 240), CV_8U, cv::Scalar(255)); // 以 (width, height) 的形式送入,Size(320, 240) 就是寬320,高 240,對應了 240 行,320 列。

// 也可以通過 size() 函數(shù)得到某個圖片的大小,然后構(gòu)造同樣大小的圖片
cv::Mat myMat(image1.size(), CV_8U, cv::Scalar(255));

OpenCV 有一套自己的數(shù)據(jù)類型名稱,與標準的 C++ 數(shù)據(jù)類型對應關(guān)系如下:

  • CV_8U -> unsigned char (min = 0, max = 255)
  • CV_8S -> char (min = -128, max = 127)
  • CV_16U -> unsigned short (min = 0, max = 65535)
  • CV_16S -> short (min = -32768, max = 32767)
  • CV_32S -> int (min = -2147483648, max = 2147483647)
  • CV_32F -> float
  • CV_64F -> double

另外,在數(shù)據(jù)類型中可以添加通道數(shù),例如 CV_8UC3 表示 3 通道的 8 位無符號整型。

用 create( ) 函數(shù)對原 Mat 重新賦值
image1.create(200, 200, CV_8U);
image1 = 255;

出于性能方面的考慮,如果新的尺寸和類型與原來相同,則不會重新分配內(nèi)存空間。

Mat 類型數(shù)據(jù)復制: copyTo( ) 和 clone( )

在復制 Mat 類型的數(shù)據(jù)時,要注意復制前后變量指向同一數(shù)據(jù)還是指向完全分離的數(shù)據(jù)。

cv::Mat image2(image1);    // 方式 1

cv::Mat image3 = image1;   // 方式 2

上述兩種方式復制得到變量 image2 和 image3 與 image1 指向相同的數(shù)據(jù)。這種形式的復制也被稱為淺復制 shallow copy,可以節(jié)省內(nèi)存空間,但這種數(shù)據(jù)關(guān)聯(lián)也可能導致意想不到的錯誤,修改其中一個,其他的也都會改變。例如,下邊這個 Test 類,其中的 method() 函數(shù)返回一個 Mat 數(shù)據(jù)類型變量 img,由于內(nèi)存共享的問題,如果一個對象修改了 img 數(shù)據(jù),那么Test 類和它生成的所有對象也會被修改,這顯然違背了面向?qū)ο缶幊讨械姆庋b原則。因此,不建議在類中返回 Mat 類型變量。如果需要的話,可以用后邊的深復制函數(shù)返回變量的一個獨立的復制。

class Test{
  cv::Mat img;
  public: 
       Test(): img(240, 320, CV_8U, 100){ }
       cv::Mat method() {return img;}
}

如果要得到完全分離的數(shù)據(jù),應該用 copyTo() 或 clone() 這兩個深復制函數(shù)明確指出:

image1.copyTo(image2);    // 方式 1

cv::Mat image2 = image1.clone();  // 方式 2
Mat 數(shù)據(jù)轉(zhuǎn)換: convertTo( )
image1.convertTo(image4, 
                 CV_32F, // 數(shù)據(jù)類型轉(zhuǎn)化成浮點型
                 1/255.0, // 縮放因子 
                 1.0);  // 偏移量。最終結(jié)果就是 x/255.0 + 1.0

OpenCV 在顯示圖像的時候,可以接受整型的數(shù)據(jù)類型,取值范圍 0 ~ 255,也可以接受浮點型的數(shù)據(jù)類型,取值范圍 0.0 ~ 1.0. 因此當把整型數(shù)據(jù)轉(zhuǎn)成浮點型時,要考慮是否需要數(shù)據(jù)范圍的縮放。例如,下面左側(cè)原圖為 CV_8UC3 類型,用 convertTo() 函數(shù)轉(zhuǎn)換成浮點型 CV_32FC3,如果數(shù)值縮放因子設置為 1,即不縮放,那么轉(zhuǎn)化之后超過 1.0 的數(shù)據(jù)均被截斷,轉(zhuǎn)化后的圖像呈現(xiàn)很多白色區(qū)域,這顯然不是我們想要的效果。


without scale.png

如果 Mat 元素很少且已知,例如輸入相機的內(nèi)參矩陣,就可以在初始化時直接輸入

cv::Mat K = (cv::Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
讀入圖片
  • 原樣讀取,不特別設定為灰度圖或者彩色圖像,把第二個參數(shù)設定為負數(shù)即可,例如:
cv::Mat image = cv::imread(“image.jpg”, -1);
  • 讀取為灰度圖:
cv::Mat image = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE);

// 或者寫對應的編號
cv::Mat image = cv::imread("image.jpg", 0);
  • 讀取為彩色圖:第二個參數(shù)設定為正數(shù),或者設定為 cv::IMREAD_COLOR

第二個參數(shù)取值總結(jié):
\begin{cases} \text{彩色} ~~~~~~ \text{正數(shù)、cv::IMREAD_COLOR}\\ \text{灰度} ~~~~~~ \text{零、cv::IMREAD_GRAYSCALE}\\ \text{原樣} ~~~~~~ \text{負數(shù)} \end{cases}

waitKey( ) 等待

一般與 imshow() 連用,效果是暫停處理,讓圖片顯示一段時間。如果不加 waitKey(),就不會顯示圖片。

cv::waitKey( int delay=0)
  • delay <= 0,無限等待
  • delay >0 ,等待 delay 時間,單位為 ms

如果在等待期間有按鍵輸入,則返回值為按鍵對應的編碼;如果等待期間沒有按鍵,則返回 -1.

整行、整列賦值
image.row(2).setTo(cv::Scalar(100));

image.col(2).setTo(cv::Scalar(100));   // 以上兩個命令針對灰度圖/單通道

image.col(2).setTo(cv::Scalar(0, 20, 34));  //彩色,OpenCV 中顏色通道默認順序為 B、G、R

// 把 src 矩陣的第 i 行 賦值給 dst 矩陣的第 j 行
src.row(i).copyTo(dst.row(j));
查找最小值、最大值及其位置
cv::minMaxLoc(src, double* minVal, double* maxVal=0, 
              Point* minLoc=0, Point* maxLoc=0, 
              InputArray mask=noArray())
  • src:輸入的圖像。
  • minVal:最小值,可輸入NULL表示不需要。
  • maxVal :最大值,可輸入NULL表示不需要。
  • minLoc:最小值的位置,可輸入NULL表示不需要,Point類型。
  • maxLoc:最大值的位置,可輸入NULL表示不需要,Point類型。
  • mask:選定在哪些區(qū)域進行搜索。noArray() 表示沒有設置 mask

如果只需要得到最小值和最大值,而不需要位置:

double my_min, my_max;
cv::minMaxLoc(input, &my_min, &my_max); // 只需要送入變量地址即可
像素運算之后截斷

當涉及到像素值之間的加、減時,為了得到有意義的結(jié)果,一般要進行截斷操作。
比如取值在 0~255 的像素,當運算結(jié)果 > 255 時,取為 255;當結(jié)果 < 0 時,取 0.

result = cv::saturate_cast<uchar>(some expression)

實際上,很多 cv 的函數(shù)默認內(nèi)置了 saturate_cast 操作,比如下邊提到的 cv::add 和重載的 + 運算。

重載運算符

比較常用的重載有

  • 加、減
  • 函數(shù) min, max, abs
  • 比較運算符 <, <=, ==, !=, >, >=,返回 8 位的二進制值。
  • 矩陣運算:乘法、求逆 image.inv()、轉(zhuǎn)置 image.t()、行列式 image.determinant()、范數(shù) image.norm()、叉乘 v1.cross(v2)、點乘 v1.dot(v2)。 這些運算都不是 in place 的,即不改變原數(shù)據(jù)。
  • 組合運算符,如 +=
opencv 中的隨機數(shù)
cv::RNG rng;
rng.uniform(0,255); // 生成范圍內(nèi)的隨機數(shù)
兩圖像疊加
cv::add(image1, image2, result)

//或者加權(quán)形式 
cv::addWeighted(image1, weight1, image2, weight2, const, result)

OpenCV 中已經(jīng)重載了 + 運算符,因此可以直接這樣:

result = weight1 * image1 + weight2 * image2 + const
圖像通道拆分與合并

將三通道拆分出來,存到 vector 中

std::vector<cv::Mat> planes;

cv::split(image1, planes);

這里用的是 c++ 模板中的 vector 數(shù)據(jù)類型。如果通道數(shù)量事先已知,也可以用數(shù)組類型,效率更高一些。

cv::Mat planes[3];

cv::split(image1, planes);

合并回去

cv::Mat result; 

cv::merge(planes, result);
閾值操作
cv::threshold(input,  // 輸入的圖片
              output,  // 輸出的圖片
              theshold, // 設定的閾值
              255,   // 超過閾值的像素值取為 255,其余為 0
              cv::THRESH_BINARY_INV); // 但這里用了 INV 反向操作,小于等于閾值的取 255,其余為 0

圖像重映射

這里的重映射是指將原圖中的元素經(jīng)過某種位置變換,得到新圖。這里只是改變了元素的位置,沒有修改元素的值。
通過 cv::remap 函數(shù)實現(xiàn)。
首先定義重映射參數(shù)矩陣

cv::Mat srcX(image.rows, image.cols, CV_32F);
cv::Mat srcY(image.rows, image.cols, CV_32F);

目標圖像在 (i,j) 處的像素來自原圖的下列位置

(srcX.at<float>(i,j), srcY.at<float>(i,j))

然后在這兩個參數(shù)矩陣中定義具體的位置映射關(guān)系

for (int i=0; i<image.rows; i++){
     for (int j=0; j<image.cols; j++){
       srcX.at<float>(i,j) = j;  // 來自原圖的 j 列
       srcY.at<float>(i,j) = i+5*sin(j/10.0);  // 來自原圖的 i+5*sin(j/10) 行,即正弦扭曲
     }
}

最后調(diào)用 cv::remap 函數(shù)

cv::remap(image, result, srcX, srcY, cv::INTER_LINEAR);
反色

對于灰度圖,直接用 255 減去就可以了!

cv::Mat result1_inv = 255 - result1;
獲取圖像元素的常用方法
  1. cv::Mat類中的 at() 方法:
image.at<uchar>(i,j); 

image.at<cv::Vec3b>(i,j)[0]

上邊尖括號中的數(shù)據(jù)類型就是灰度圖和彩圖中常用的數(shù)據(jù)類型:ucharcv::Vec3b。

在使用 at() 時有一個需要注意的問題:
at() 函數(shù)有多個重載的形式,例如既有 at(int row, int col),也有 at(Point2f()) 。Point2f 格式對應的是 (x,y) 坐標,x 軸向右,y 軸向下,與 (row, col) 正好顛倒。因此在調(diào)用時一定要注意參數(shù)是 (row, col) 形式還是 Point2f(x,y) 坐標形式。

  1. 每次用 at 必須指明元素的數(shù)據(jù)類型,比較麻煩??梢杂昧硪环N方式:
cv::Mat_<uchar> img(image);

img(50, 10)=200;

即在定義變量時就已經(jīng)指明了元素類型,后邊直接取用就可以了。

數(shù)據(jù)類型的編號
cv::Mat test = cv::Mat::zeros(3,3,CV_64F);

test.type();  // 返回 6

.type() 函數(shù)即可返回 Mat 的數(shù)據(jù)類型,但是返回的是一個整數(shù),需要查看下表確定到底是什么類型:

C1 C2 C3 C4
CV_8U 0 8 16 24
CV_8S 1 9 17 25
CV_16U 2 10 18 26
CV_16S 3 11 19 27
CV_32S 4 12 20 28
CV_32F 5 13 21 29
CV_64F 6 14 22 30

列代表通道數(shù)目,例如灰度圖就是 C1, jpg 彩圖就是 BGR C3,PNG 彩圖除了 BGR 還有一個透明度通道,所以是 C4。

在用.at<> 獲取 Mat 中的數(shù)據(jù)時,需要指明元素的數(shù)據(jù)類型,比如灰度圖是 uchar 而不是 CV_8U,查看下表:

C1 C2 C3 C4 C6
uchar uchar cv::Vec2b cv::Vec3b cv::Vec4b
short short cv::Vec2s cv::Vec3s cv::Vec4s
int int cv::Vec2i cv::Vec3i cv::Vec4i
float float cv::Vec2f cv::Vec3f cv::Vec4f cv::Vec6f
double double cv::Vec2d cv::Vec3d cv::Vec4d cv::Vec6d

這里的 cv::Vec3bb 是 byte 的意思,表示占用一個字節(jié)。

顏色空間的轉(zhuǎn)換

cv::cvtColor 轉(zhuǎn)換前后,圖像的數(shù)據(jù)類型是相同的。

  • 彩圖到灰度:
if (image.channels()==3){
     cv::cvtColor(image, result, cv::COLOR_BGR2GRAY);
     // 老版本的 opencv 用這個 flag
     // cv::cvtColor(color, gray, CV_BGR2Gray)
}
  • 灰度到彩圖:
cv::cvtColor(image, result, cv::COLOR_Gray2BGR) // 三個通道數(shù)值相同

cv::cvtColor( )函數(shù)可以實現(xiàn)很多顏色空間的轉(zhuǎn)換,除了 BGR、灰度圖,還有 HSV, HLS,Lab, Luv, YCrCb 等。

感知均勻的色彩空間

原始的 BGR 顏色表示方法有個缺陷:在顏色空間中的差距并不能很好的反映人視覺感知的差距。
兩種在空間中距離很遠的顏色可能肉眼看起來很接近;而有些在空間距離很近的顏色,看起來差別卻很大。因此,BGR 不是感知均勻的色彩空間。

下邊是兩種感知均勻的色彩空間:

  • CIE L * a * b *
    L 通道:表示亮度,取值 0~100。在使用 8 位圖像時,取值 0~255
    a 通道和 b 通道:表示色度部分,與亮度完全無關(guān),取值 -127~127,對于 8 位圖,取值 0~255.
    cv::cvtColor(image, converted, cv::COLOR_BGR2Lab)
    
  • CIE L * u * v *
    對亮度通道采用相同的轉(zhuǎn)換公式,但對色度通道使用不同的方法。
    cv::cvtColor(image, converted, cv::COLOR_BGR2Luv)
    
色調(diào)、飽和度、亮度空間

人類在描述顏色時,經(jīng)常說色調(diào)、飽和度、亮度等詞語,下邊的兩種色彩空間就是這樣設計的:

  • HSV
    H: 色調(diào) hue
    S: 飽和度 saturation
    V: 明度 value

    cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV)
    

    可以把三通道分割開:

    std::vector<cv::Mat> channels;
    
    cv::split(hsv, channels);
    // channels[0] 表示色調(diào)  0~180
    // channels[1] 表示飽和度  0~255
    // channels[2] 表示亮度  0~255
    
  • HLS
    H: 色調(diào) hue
    L: 亮度 lightness
    S: 飽和度 saturation

HSV 和 HLS 有時也統(tǒng)稱為 HSB ,最后的 B 表示 brightness 亮度。
可以用圓錐體直觀地表示 HSB 色彩空間:

HSB_1.png
  • 角度表示色調(diào) H,本來應該用 0~360 表示色調(diào),但是為了適應 8 位圖,采用了 0~180,0 度對應紅色。
  • 距中軸線的距離表示飽和度 S
  • 高度表示亮度 B

如果要進行圖片的 HSB 分析,尤其是涉及到色調(diào)(第一個分量)的時候,由于飽和度(第二個分量)低的區(qū)域色調(diào)信息不準確,因此往往先對飽和度做一些過濾,只分析那些飽和度比較高的區(qū)域的色調(diào)。

區(qū)域圖像提取
cv::Mat imageROI = image(cv::Rect(216, 33, 24, 30));  // 從原始圖片 image 中提取了一個小方塊

// 或者
cv::Mat imageROI(image2, cv::Rect(0,0,image2.cols,image2.rows/2)); 

隨后就可以把提取出來的 imageROI 當成普通圖片處理了。

添加幾何圖形和文字

外加圖形和文字的顏色維度取決于原始圖像的維度。
若原始為灰度圖,則設定外加顏色時,即使寫出三維像素值 cv::Scalar(255, 0, 128) 也只看第一維的數(shù)值;
若原始為彩圖,則設定外加顏色時,只寫 0 或者 255 表示只設定了第一個顏色通道的值,例如 255 相當于 cv::Scalar(255, 0, 0)

  • 線段

    cv::line(image, 
             cv::Point(5, 5), 
             cv::Point(210, 200),  // 線段的兩個端點
             cv::Scalar(255), // 顏色
             1); // 粗細
    
  • 矩形方框

    cv::Rect rect(110, 45, 35, 45); // 設定一個方框,左上角坐標+寬+長
    
    cv::rectangle(image, rect, cv::Scalar(0,0,255));  // 在 image 圖像上添加 rect 方框,紅色
      
    //或者直接把矩形放在內(nèi)部定義
    cv::rectangle(image, cv::Rect(110, 45, 35, 45), 0); 
      
    // 或者以兩對點集來表示,分別為左上角坐標和右下角坐標
    cv::rectangle(image, 
                  cv::Point(5,5),  // 左上角坐標
                  cv::Point(210, 200), // 右下角坐標
                  cv::Scalar(255),  // 顏色
                  3); // 線條粗細
    
  • cv::circle(image,   // 原始圖像
               cv::Point(155, 110),  // 圓心, 以 cv::Point(x,y) 表示圖像中的坐標
               65,   // 半徑,必須為 int 類型
               0,   // 顏色
               3);  // 線條寬度,若為負數(shù),則畫實心圓
    
  • 加重心
    假設圖像中物體輪廓已經(jīng)找到了,要依據(jù)輪廓添加物體的重心位置。
    一般計算重心的時候,首先要計算圖像的矩 (moment),再基于各階矩計算出重心:

    itc = contours.begin();  // 已經(jīng)有了若干輪廓線
    
    while (itc != contours.end()){
         cv::Moments mom = cv::moments(cv::Mat(*itc++)); // 求每條輪廓的各階矩
         cv::circle(result, 
                    cv::Point(mom.m10/mom.m00, mom.m01/mom.m00), // 通過這種方式計算重心
                    2,
                    cv::Scalar(0),
                    2
         );
    }
    

    原理:
    對于二元連續(xù)函數(shù) f(x,y),它的 (p+q) 階矩為
    M_{pq} = \int_{-\infty}^{\infty}\int_{-\infty}^{\infty}x^py^qf(x,y)dxdy

    對于離散的圖片 I(x,y),對應的 (p+q) 階矩為
    M_{ij} = \sum_x \sum_y x^i y^i I(x,y)

    因此,圖片 I(x,y) 的重心為
    (\bar{x},\bar{y}) = \left( \frac{M_{10}}{M_{00}}, \frac{M_{01}}{M_{00}} \right)

  • 橢圓
    可以通過外包矩形的方式畫出橢圓

    std::vector<cv::Point> points;
    
    cv::RotatedRect rr = cv::minAreaRect(points); // 由點集得到最小矩形外包,可以傾斜
    
    cv::ellipse(image, rr, 255, 2); 
    

    也可以直接指定橢圓的形狀參數(shù)

    cv::ellipse(image, 
                cv::Point(WINDOW_WIDTH / 2, WINDOW_WIDTH / 2),  // 橢圓中心點
                cv::Size(WINDOW_WIDTH / 4, WINDOW_WIDTH / 16), // 兩個軸的長度
                angle, // 橢圓傾斜的角度
                0, 360,  // 橢圓包圍的起始和終止角度
                Scalar(255, 129, 0),
                thickness);
    
  • 多邊形

    std::vector<cv::Point> poly; // 存放多邊形的頂點坐標
    
    cv::polylines(image, 
                  poly, 
                  true, // 是否閉合,若閉合,則最后一個點將于第一個點相連
                  0,  // 顏色
                  2);  // 寬度
    
  • 加文字

    cv::putText(image, 
                "This is a dog.",
                cv::Point(40, 200),  // 文本位置,文本的左下角對應的坐標
                cv::FONT_HERSHEY_PLAIN, // 字體類型
                2.0,  // 字體大小
                255,  // 字體顏色,這里相當于  cv::Scalar(255, 0, 0)
                2);  // 字體粗細
    
直線擬合

由點集擬合出一條直線。一般的擬合目標是使所有點到直線的距離之和最小。
在計算距離的函數(shù)中,歐幾里得距離計算的最快,對應參數(shù) cv::DIST_L2,實際上也就是基于最小二乘算法的擬合。

std::vector<cv::Point> points;  // 假設我們已經(jīng)獲取了節(jié)點集合

cv::Vec4f line; // 待擬合的直線

cv::fitLine(points,   // 點集
            line,    // 擬合出的直線
            cv::DIST_L2,  // 采用的距離類型
            0,  // L2 距離不需要這個
            0.01, 
            0.01); // 擬合出的直線參數(shù)精度

直線是 cv::Vec4f 的格式,包含四個數(shù),前兩個數(shù)表示單位向量方向,后兩個數(shù)表示直線上的一個坐標。
也可以在三維空間中擬合直線,只需要將點集和直線的數(shù)據(jù)類型相應地修改一下:

std::vector<cv::Point3i> points;

// 或者
std::vector<cv::Point3f> points;

cv::Vec6f line;
改變圖片大小 resize
cv::resize(image, 
           result, 
           cv::Size(), // 指定改變之后的大小
           4, 4,  // 指定 x,y 方向(即 width, height)縮放比例。本行與上一行至少要設定一個
           cv::INTER_NEAREST); // 插值方式

圖片大小改變后,像素值要重新計算,有以下插值方法:

  • INTER_NEAREST - 最鄰近插值,對于放大圖像的情況,就是簡單的把每個像素的尺寸放大。
  • INTER_LINEAR - 雙線性插值,如果最后一個參數(shù)不指定,默認使用這種方法。
    所謂雙線性,就是先在插入點的兩側(cè)線性插入新像素值,再以此為基礎,再次線性的插入像素值。整個過程中用到 2x2 = 4 個原始像素。


    interpolation1.png
  • INTER_CUBIC - 4x4像素鄰域內(nèi)的雙立方插值
  • INTER_LANCZOS4 - 8x8像素鄰域內(nèi)的Lanczos插值

以上插值算法計算復雜度依次增加,效果也是依次提升的。

掃描圖像元素的方法
  • .at 函數(shù)
    // 減色運算操作
    for(int j=0; j<nl; j++){  // nl 為行數(shù)
       for(int i=0; i<nc; i++){  // nc 為列數(shù)
         image.at<cv::Vec3b>(j,i)[0] = image.at<cv::Vec3b>(j,i)[0]/div * div + div/2;
         image.at<cv::Vec3b>(j,i)[1] = image.at<cv::Vec3b>(j,i)[0]/div * div + div/2;
         image.at<cv::Vec3b>(j,i)[2] = image.at<cv::Vec3b>(j,i)[0]/div * div + div/2;
       }
    }
    
  • 用指針
    如果要按順序依次獲取圖像像素信息,盡量不要用上述 image.at 方法,而要用指針,指針在移動掃描操作時效率更高。
    int nc = image.cols * image.channels();
    for (int j=0; j<nl; j++){
       uchar *data = image.ptr<uchar>(j); // 每行首元素對應的地址
       for (int i=0; i<nc; i++){
         data[i] = data[i]/div*div + div/2;
       }
    }
    

另外,少層循環(huán) + 每個循環(huán)中較多操作 好于 多層循環(huán) + 每個循環(huán)中較少操作。
例如要對一個像素執(zhí)行 N 個不同的操作,就應該在單個循環(huán)中執(zhí)行全部操作,而不是寫 N 個循環(huán),每個循環(huán)執(zhí)行一個操作。

  • 用迭代器 (iterator)
    cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
    
    cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();
    
    for (;it!= itend;++it){
       (*it)[0] = ...
       (*it)[1] = ...
       (*it)[2] = ...
    }
    
    //或者用 while 循環(huán)
    while (it!=itend){
       ...
       ++it;
    }
    
減色算法

默認 BGR 三色都是采用 8 位二進制數(shù),即有 0~255 種選擇,三通道互相搭配總共的顏色數(shù)目為 256 * 256 * 256。 為了降低分析的復雜度,可以先對原圖做減色處理,將每個通道顏色的數(shù)量降低為原來的 1/n,例如 n=8,則新的圖片總共的顏色數(shù)目只有 32 * 32 * 32 。

算法步驟:

  • 假設 N 為減色因子,將圖像中每個像素的值除以 N,去掉余數(shù)部分
  • 將上述結(jié)果乘以 N,得到原始像素值相對于 N 的倍數(shù)(相當于向下取整)
  • 再加上 N/2,這樣相對于原始像素值的誤差就從 -N ~ 0 變成了 -N/2 ~ N/2 ,近似效果更好
  • 減色之后共有 (256/N) * (256/N) * (256/N) 種顏色。
銳化圖像

目的是放大圖像邊緣,使圖像看起來更加尖銳
對于每個像素,令本身像素值乘以5,然后減去周圍的 4 個像素值,即
sharpended\_pixel = 5*current - left - right - up -down
這種操作實際上就是一種卷積

sharpen_kernel.png

用上述 kernel matrix 依次掃過圖像,即完成了上述的銳化操作。

cv::Mat 數(shù)據(jù)類型作為參數(shù)傳遞

cv::Mat 數(shù)據(jù)結(jié)構(gòu)包括數(shù)據(jù)頭部和數(shù)據(jù)塊:

  • 數(shù)據(jù)頭部包含了屬性信息,例如數(shù)據(jù)的大小、行列、通道數(shù)、元素類型等,可以通過 .cols,.rows, channels() 等方式獲取數(shù)據(jù)頭部包含的信息;
  • 數(shù)據(jù)塊存儲了所有的像素信息,數(shù)據(jù)頭部中包含了 .data 指針,可以訪問數(shù)據(jù)塊。

cv::Mat 數(shù)據(jù)結(jié)構(gòu)的一個關(guān)鍵特點時,當復制一個 cv::Mat 數(shù)據(jù)時,僅復制了數(shù)據(jù)頭部,因此復制前后的變量都指向同一個數(shù)據(jù)塊(除非特別指明數(shù)據(jù)頭部和數(shù)據(jù)塊全都復制)。前述內(nèi)容中提到,copyTo(), clone() 函數(shù)才會復制得到全新的函數(shù),而用賦值操作僅僅復制了數(shù)據(jù)頭部,數(shù)據(jù)塊仍然是共享的。

當向函數(shù)中傳遞 cv::Mat 類型數(shù)據(jù)時,比較常見的參數(shù)傳遞形式包括 cv::Mat, const cv::Mat, const cv::Mat &, cv::Mat &.

  1. func(cv::Mat input) 僅僅復制了 input 對應實參變量的數(shù)據(jù)頭部,對 input 的操作會改變實參變量。但是如果對 input 重新賦值,例如 input = cv::Mat::ones(...),此時對 input 的操作不會影響到外部的實參,因為新的 input 不再指向?qū)崊⒆兞康臄?shù)據(jù)塊。
  2. func(const cv::Mat input) 也是僅僅復制了 input 對應實參的數(shù)據(jù)頭部,只不過由于 const 的限制,不能在函數(shù)內(nèi)部修改該數(shù)據(jù)頭部。
  3. func(const cv::Mat &input) 以引用的形式傳遞了實參變量的數(shù)據(jù)頭部,同樣由于 const 的限制,不能修改該頭部。
  4. func(cv::Mat &) 以引用的形式傳遞了實參變量的數(shù)據(jù)頭部,并且可以修改該頭部。

注意:上述參數(shù)傳遞方式僅僅對數(shù)據(jù)頭部做了限制,而不限制頭部指向的數(shù)據(jù)塊,因此情況 2 和 3 中,即使由于 const 限制不能修改頭部,仍然可以修改頭部指向的數(shù)據(jù)塊: input.data[0] = 5

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容