OpenCV矩陣上的掩碼操作

2018-08-29 11:48 更新

矩陣上的掩碼操作非常簡單。這個想法是,我們根據(jù)掩碼矩陣(也稱為內(nèi)核)重新計算圖像中的每個像素值。該掩碼保存將調(diào)整相鄰像素(和當前像素)對新像素值有多大影響的值。從數(shù)學的角度來看,我們用加權(quán)平均值與我們指定的值進行比較。

我們的測試案例 

C++

讓我們考慮圖像對比度增強方法的問題?;旧衔覀円獮閳D像的每個像素應用以下公式:

QQ圖片20170829112817

第一個符號是使用公式,而第二個是通過使用掩碼的第一個壓縮版本。通過將掩模矩陣的中心(在零值索引的大寫表示)放在要計算的像素上,并使用疊加的矩陣值乘以像素值,并使用掩碼。這是同樣的事情,但是在大型矩陣的情況下,后一種符號更容易查看。

現(xiàn)在讓我們看看如何通過使用基本的像素訪問方法或使用cv :: filter2D函數(shù)來實現(xiàn)這一點。

基本方法 

這里有一個功能:

void Sharpen(const Mat& myImage,Mat& Result)
{
    CV_Assert(myImage.depth() == CV_8U);  // accept only uchar images
    const int nChannels = myImage.channels();
    Result.create(myImage.size(),myImage.type());
    for(int j = 1 ; j < myImage.rows-1; ++j)
    {
        const uchar* previous = myImage.ptr<uchar>(j - 1);
        const uchar* current  = myImage.ptr<uchar>(j    );
        const uchar* next     = myImage.ptr<uchar>(j + 1);
        uchar* output = Result.ptr<uchar>(j);
        for(int i= nChannels;i < nChannels*(myImage.cols-1); ++i)
        {
            *output++ = saturate_cast<uchar>(5*current[i]
                         -current[i-nChannels] - current[i+nChannels] - previous[i] - next[i]);
        }
    }
    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));
}

首先我們確保輸入圖像數(shù)據(jù)是unsigned char格式。為此,我們使用cv :: CV_Assert函數(shù),當其中的表達式為false時,該函數(shù)會引發(fā)錯誤。

    CV_Assert(myImage.depth() == CV_8U);  // accept only uchar images

我們創(chuàng)建一個與我們的輸入相同大小和相同類型的輸出圖像。您可以在存儲部分看到,根據(jù)通道數(shù)量,我們可能有一個或多個子列。

我們將通過指針迭代它們,因此元素的總數(shù)取決于這個數(shù)字。

    const  int nChannels = myImage.channels();
    Result.create(myImage.size(),myImage.type());

我們將使用普通C []運算符來訪問像素。因為我們需要在同一時間訪問多行,我們將獲取每個行的指針(前一個,當前和下一行)。我們需要另一個指向我們要保存計算的指針。然后只需使用[]運算符訪問正確的項目。為了在前面移動輸出指針,我們在每個操作之后簡單地增加一個(一個字節(jié)):

    for(int j = 1; j <myImage.rows-1; ++ j)
    {
        const  uchar * previous = myImage.ptr < uchar >(j  -  1);
        const  uchar * current = myImage.ptr < uchar >(j);
        const  uchar * next = myImage.ptr < uchar >(j + 1);
        uchar * output = Result.ptr < uchar >(j);
        for(int i = nChannels; i <nChannels *(myImage.cols-1); ++ i)
        {
            * output ++ = saturate_cast < uchar >(5 * current [i]
                         -current [i-nChannels]  -  current [i + nChannels]  - 上一個[i]  -  next [i]);
        }
    }

在圖像的邊框上,上面的符號會導致像素位置不一致(如減去一個減去一個)。在這些點上,我們的公式是未定義的。一個簡單的解決方案是在這些點上不應用內(nèi)核,例如,將邊框上的像素設(shè)置為零:

    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));

filter2D function 

應用這樣的過濾器在圖像處理中是常見的,在OpenCV中存在著將應用掩碼(在某些地方也稱為內(nèi)核)的功能。為此,您首先需要定義一個保存掩碼的對象:

    Mat kernel = (Mat_<char>(3,3) <<  0, -1,  0,
                                   -1,  5, -1,
                                    0, -1,  0);

然后調(diào)用cv :: filter2D函數(shù),指定輸入,輸出圖像和內(nèi)核使用:

    filter2D(src,dst1,src.depth(),kernel);

該函數(shù)甚至有第五個可選參數(shù)來指定內(nèi)核的中心,第六個可選參數(shù),用于在將其存儲在K中之前添加可選值,然后將其存儲在K中,第七個用于確定在操作未定義的區(qū)域中要執(zhí)行的操作(國界)。

此功能較短,較少冗長,因為有一些優(yōu)化,通常比手工編碼方法更快。例如在我的測試中,第二個只花了13毫秒,第一次花費了大約31毫秒。有一些區(qū)別。

例如:

resultMatMaskFilter2D

您可以從這里下載此源代碼,或查看OpenCV源代碼庫示例目錄samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp。

Java

讓我們考慮圖像對比度增強方法的問題?;旧衔覀円獮閳D像的每個像素應用以下公式:

QQ圖片20170829113212

第一個符號是使用公式,而第二個是通過使用掩碼的第一個壓縮版本。通過將掩模矩陣的中心(在零值索引的大寫表示)放在要計算的像素上,并使用疊加的矩陣值乘以像素值,并使用掩碼。這是同樣的事情,但是在大型矩陣的情況下,后一種符號更容易查看。

現(xiàn)在讓我們看看如何通過使用基本的像素訪問方法或者使用Imgproc.filter2D()函數(shù)來實現(xiàn)這一點。

基本方法 

這里有一個功能:

    public static double saturate(double x) {
        return x > 255.0 ? 255.0 : (x < 0.0 ? 0.0 : x);
    }
    public Mat sharpen(Mat myImage, Mat Result) {
        myImage.convertTo(myImage, CvType.CV_8U);
        int nChannels = myImage.channels();
        Result.create(myImage.size(), myImage.type());
        for (int j = 1; j < myImage.rows() - 1; ++j) {
            for (int i = 1; i < myImage.cols() - 1; ++i) {
                double sum[] = new double[nChannels];
                for (int k = 0; k < nChannels; ++k) {
                    double top = -myImage.get(j - 1, i)[k];
                    double bottom = -myImage.get(j + 1, i)[k];
                    double center = (5 * myImage.get(j, i)[k]);
                    double left = -myImage.get(j, i - 1)[k];
                    double right = -myImage.get(j, i + 1)[k];
                    sum[k] = saturate(top + bottom + center + left + right);
                }
                Result.put(j, i, sum);
            }
        }
        Result.row(0).setTo(new Scalar(0));
        Result.row(Result.rows() - 1).setTo(new Scalar(0));
        Result.col(0).setTo(new Scalar(0));
        Result.col(Result.cols() - 1).setTo(new Scalar(0));
        return Result;
    }

首先我們確保輸入圖像數(shù)據(jù)以無符號8位格式。

        myImage.convertTo(myImage,CvType .CV_8U);

我們創(chuàng)建一個與我們的輸入相同大小和相同類型的輸出圖像。您可以在存儲部分看到,根據(jù)通道數(shù)量,我們可能有一個或多個子列。

        int nChannels = myImage.channels();
        Result.create(myImage.size(),myImage.type());

我們需要訪問多個行和列,可以通過向當前中心(i,j)添加或減去1來完成。然后我們應用總和并將新值放在結(jié)果矩陣中。

        for(int j = 1; j <myImage.rows() -  1; ++ j){
            for(int i = 1; i <myImage.cols() -  1; ++ i){
                double sum [] = new  double [nChannels];
                for(int k = 0; k <nChannels; ++ k){
                    double top = -myImage.get(j  -  1,i)[k];
                    double bottom = -myImage.get(j + 1,i)[k];
                    double center =(5 * myImage.get(j,i)[k]);
                    double left = -myImage.get(j,i-1)[k];
                    double right = -myImage.get(j,i + 1)[k];
                    sum [k] =飽和(頂+底+中+左+右);
                }
                Result.put(j,i,sum);
            }
        }

在圖像的邊框上,上面的符號會導致像素位置不存在(如(-1,-1))。在這些點上,我們的公式是未定義的。一個簡單的解決方案是在這些點上不應用內(nèi)核,例如,將邊框上的像素設(shè)置為零:

        Result.row(0).setTo(new  Scalar(0));
        Result.row(Result.rows() -  1).setTo(new  Scalar(0));
        Result.col(0).setTo(new  Scalar(0));
        Result.col(Result.cols() -  1).setTo(new  Scalar(0));

filter2D function 

應用這樣的過濾器在圖像處理中是常見的,在OpenCV中存在著將應用掩碼(在某些地方也稱為內(nèi)核)的功能。為此,您首先需要定義一個保存掩碼的對象:

        Mat kern = new Mat(3,3,CvType .CV_8S);
        int row = 0,col = 0;
        kern.put(row,col,0,-1,0,-1,5,-1,0,-1,0);

然后調(diào)用Imgproc.filter2D()函數(shù),指定要使用的輸入,輸出圖像和內(nèi)核:

        Imgproc.filter2D(src,dst1,src.depth(),kern);

該函數(shù)甚至有第五個可選參數(shù)來指定內(nèi)核的中心,第六個可選參數(shù),用于在將其存儲在K中之前添加可選值,然后將其存儲在K中,第七個用于確定在操作未定義的區(qū)域中要執(zhí)行的操作(國界)。

此功能較短,較少冗長,因為有一些優(yōu)化,通常比手工編碼方法更快。例如在我的測試中,第二個只花了13毫秒,第一次花費了大約31毫秒。有一些區(qū)別。

例如:

resultMatMaskFilter2D (1)

您可以在OpenCV源代碼庫示例目錄中查看samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java。

Python

我們考慮圖像對比度增強方法的問題?;旧衔覀円獮閳D像的每個像素應用以下公式:

QQ圖片20170829114349

第一個符號是使用公式,而第二個是通過使用掩碼的第一個壓縮版本。通過將掩模矩陣的中心(在零值索引的大寫表示)放在要計算的像素上,并使用疊加的矩陣值乘以像素值,并使用掩碼。這是同樣的事情,但是在大型矩陣的情況下,后一種符號更容易查看。

現(xiàn)在讓我們看看如何通過使用基本的像素訪問方法或使用cv2.filter2D()函數(shù)來實現(xiàn)這一點。

基本方法 

這里有一個功能:

def is_grayscale(my_image):
    return len(my_image.shape) < 3
def saturated(sum_value):
    if sum_value > 255:
        sum_value = 255
    if sum_value < 0:
        sum_value = 0
    return sum_value
def sharpen(my_image):
    if is_grayscale(my_image):
        height, width = my_image.shape
    else:
        my_image = cv2.cvtColor(my_image, cv2.CV_8U)
        height, width, n_channels = my_image.shape
    result = np.zeros(my_image.shape, my_image.dtype)
    
    for j in range(1, height - 1):
        for i in range(1, width - 1):
            if is_grayscale(my_image):
                sum_value = 5 * my_image[j, i] - my_image[j + 1, i] - my_image[j - 1, i] \
                            - my_image[j, i + 1] - my_image[j, i - 1]
                result[j, i] = saturated(sum_value)
            else:
                for k in range(0, n_channels):
                    sum_value = 5 * my_image[j, i, k] - my_image[j + 1, i, k] - my_image[j - 1, i, k] \
                                - my_image[j, i + 1, k] - my_image[j, i - 1, k]
                    result[j, i, k] = saturated(sum_value)
    
    return result

首先我們確保輸入圖像數(shù)據(jù)以無符號8位格式。

my_image = cv2.cvtColor(my_image,cv2.CV_8U)

我們創(chuàng)建一個與我們的輸入相同大小和相同類型的輸出圖像。您可以在存儲部分看到,根據(jù)通道數(shù)量,我們可能有一個或多個子列。

height,width,n_channels = my_image.shape
result = np.zeros(my_image.shape,my_image.dtype)

我們需要訪問多個行和列,可以通過向當前中心(i,j)添加或減去1來完成。然后我們應用總和并將新值放在結(jié)果矩陣中。

    for j in range(1, height - 1):
        for i in range(1, width - 1):
            if is_grayscale(my_image):
                sum_value = 5 * my_image[j, i] - my_image[j + 1, i] - my_image[j - 1, i] \
                            - my_image[j, i + 1] - my_image[j, i - 1]
                result[j, i] = saturated(sum_value)
            else:
                for k in range(0, n_channels):
                    sum_value = 5 * my_image[j, i, k] - my_image[j + 1, i, k] - my_image[j - 1, i, k] \
                                - my_image[j, i + 1, k] - my_image[j, i - 1, k]
                    result[j, i, k] = saturated(sum_value)

The filter2D function 

應用這樣的過濾器在圖像處理中是常見的,在OpenCV中存在著將應用掩碼(在某些地方也稱為內(nèi)核)的功能。為此,您首先需要定義一個保存掩碼的對象:

    kernel = np.array([[0, -1, 0],
                       [-1, 5, -1],
                       [0, -1, 0]], np.float32)  # kernel should be floating point type

然后調(diào)用cv2.filter2D()函數(shù),指定要使用的輸入,輸出圖像和kernell:

    dst1 = cv2.filter2D(src, -1, kernel)  # ddepth = -1, means destination image has depth same as input image

此功能較短,較少冗長,因為有一些優(yōu)化,通常比手工編碼方法更快。例如在我的測試中,第二個只花了13毫秒,第一次花費了大約31毫秒。有一些區(qū)別。

例如:

resultMatMaskFilter2D (2)

您可以在OpenCV源代碼庫示例目錄中查看samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號