Opencv Matピクセル操作

12850 ワード

1 cv::Mat


cv::Matはで.
 
class CV_EXPORTS Mat
{
public:
    //a lot of methods
/*! includes several bit-fields:
         - the magic signature
         - continuity flag
         - depth
         - number of channels
     */
    int flags;
    //! the matrix dimensionality, >= 2
    int dims;
    //! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
    int rows, cols;
    //! pointer to the data
    uchar* data;

    //! pointer to the reference counter;
    // when matrix points to user-allocated data, the pointer is NULL
    int* refcount;
    //ohter members
};

 
OpenCV 2はコード構造を再配置したため、すべてのクラスとメソッドは名前空間cvに定義され、名前空間を事前に定義することができる.
using namespace cv

一般的なcppプログラムと同様に,クラスのパラメータ伝達に対して参照伝達方式を採用し,より良い効率を得た.クラスには独自のコンストラクション関数とコンストラクション関数があり、メモリの漏洩を防止します.また、デフォルトのコピーコンストラクション関数はshallow copy(浅いコピー)を採用しており、deap copy(深いコピー)が必要な場合はcv::MatのcopyTo()メソッドに助けを求めることができる.これらはすべてcppの基礎知識です

2 cv::Mat_


    cv::Mat_で.
template<typename _Tp> class CV_EXPORTS Mat_ : public Mat
{
public:
    //some specific methods
};

cv::Matクラスには多くのテンプレートメソッドが含まれているため、これらのパラメータタイプは実行期間まで決定されませんが、この柔軟性により簡単な呼び出しコードが複雑になるため、cv::Mat_クラスはコードを簡略化します.のように
cv::Mat image = cv::imread('img.jpg');
image.at<uchar>(j, i) = 255;

cv::Mat_<uchar> im2 = image;
im2(j, i) = 255;

コードは明らかにきれいになった.

3 Scanning an image


color Reduction操作を例にとると、ポインタ方式コードは以下の通りである.
 
/**
* An example of color reduction for scanning an image with pointers
* div = 2^n
*
*/
void colorReduce(const cv::Mat& image, cv::Mat& result, int div)
{
    int nl = image.rows;
    int nc = image.cols * image.channels();

    if (image.isContinuous()) {
        nc = nc * nl;
        nl = 1;
    }

    int n = static_cast<int>(
        log(static_cast<double>(div)) / log(2.0));

    for (int j = 0; j < nl; j++) {
        // get the addresses of input and output row
        const uchar *data_in = image.ptr<uchar>(j); //give you the address of an image row
        uchar *data_out = result.ptr<uchar>(j);

        for (int i = 0; i < nc; i++) {
            
            //slowest
            data[i] = data[i] - data[i] % div + div / 2;
            //middle
            data[i] = data[i] / div * div + div / 2;
            //best
            uchar mask = 0xFF << n; //div = 16, n = 4, mask = 0xF0
            data_out[i] = (data_in[i] & mask) + div / 2; //data[i] - data[i] % div + div / 2
        }
    }
}

 
(1)上はPointer方式で遍歴している.cv::Matクラスのテンプレートメソッドptr(int)を呼び出して画像マトリクスの行ポインタを得る.(2)3つの異なる効率の呼び出し方式:slowestは2回のメモリ読み取り操作によって時間が増加した.bestはビット演算によりテールオフ操作を行い,効率は自然に高いが,2のn次方に制限しなければならない.(3)参照伝達であるため、入力画像imageを保持するには、出力画像を保存するために入力パラメータにresultを1つ追加する.
以下は、より高速な方法です.
 
void colorReduce_f(cv::Mat& image, int div)
{
    int nl = image.rows;
    int nc = image.cols;

    if (image.isContinuous()) {
        nc = nc * nl;
        nl = 1;
    }

    int n = static_cast<int>(
        log(static_cast<double>(div)) / log(2.0));
    
    uchar mask = 0xff << n;

    for (int j = 0; j < nl; j++) {
        uchar *data = image.ptr<uchar>(j);
        
        for (int i = 0; i < nc; i++) {
            *data++ = *data & mask + div / 2;
            *data++ = *data & mask + div / 2;
            *data++ = *data & mask + div / 2;
        }    
    }
}

 
ここで、isContinuous()メソッドは、fftが2^nまで補正されるような追加の補正があるか否かを判断し、連続的であれば直接1次元配列として処理することができる.また、効率を向上させるために、サイクルごとに3回連続して実行します.(詳しくは時空の局所的な原理と関係があるはずだ)
反復方式は次のとおりです.
 
void colorReduce_2(cv::Mat& image, int div)
{
    //obtain iterator
    cv::Mat_<cv::Vec3b>::iterator iter = 
        image.begin<cv::Vec3b>(); //a template method
    cv::Mat_<cv::Vec3b>::iterator iterd = 
        image.end<cv::Vec3b>(); //a template method

    //do not use template method, more efficient
    cv::Mat_<cv::Vec3b> cimage = image;
    cv::Mat_<cv::Vec3b>::iterator iter = cimage.begin();
    cv::Mat_<cv::Vec3b>::iterator iterd = cimage.end();


    for (; iter != iterd; ++iter) {
        (*iter)[0] = (*iter)[0] / div * div + div / 2;
        (*iter)[1] = (*iter)[1] / div * div + div / 2;
        (*iter)[2] = (*iter)[2] / div * div + div / 2;
    }
}

 
ここでは、cv::Matのテンプレートメソッドbegin()とend()を直接呼び出す2つの反復器を得る方法を示します.もう1つはcv::Mat_を選択します.この点はstlライブラリと互換性があります.注意カラー画像の場合、反復器が指すcv::Vec 3 bタイプの三元グループ.

4 Scanning an image with neighbor access


方法はやはりポインタを使って、効率が高いため、しかし基本的なものはやはり不変で、コードは以下の通りです:
 
/**
*An example of Sharpen for scanning an image with neighbor access
*/
void sharpen(const cv::Mat& image2, cv::Mat& result)
{
    cv::Mat image;
    cv::cvtColor(image2, image, CV_BGR2GRAY);
    result.create(image.size(), image.type());

    for (int j = 1; j < image.rows - 1; j++) {

        const uchar* previous = 
            image.ptr<const uchar>(j - 1);
        const uchar* current = 
            image.ptr<const uchar>(j);
        const uchar* next = 
            image.ptr<const uchar>(j + 1);
        
        uchar* output = result.ptr<uchar>(j);

        for (int i = 1; i < image.cols - 1; i++) {
            
            *output++ = cv::saturate_cast<uchar> (
                5 * current[i] - current[i - 1]
                -current[i + 1] - previous[i] - next[i]);
        }
    }

    result.row(0).setTo(cv::Scalar(0));
    result.row(result.rows - 1).setTo(cv::Scalar(0));
    result.col(0).setTo(cv::Scalar(0));
    result.col(result.cols - 1).setTo(cv::Scalar(0));
}

 
これはラプラス演算子に基づく空間領域鋭化動作であり,テンプレート(kernel)は3次行列である.ここでは、で宣言します.ここではcv::saturate_も使用されています.cast()テンプレート法は,フィルタリングにおけるベル効果の除去など,得られた値が有意義な値領域範囲内であることを保証する.
Opencvではcv::filter 2 D()関数も提供され、fft 2()の方法が採用されていると推定され、テンプレート(kernel)が大きい場合にこの関数が採用され、コードは以下の通りである.
 
void sharpen2D(const cv::Mat& image2, cv::Mat& result)
{
    cv::Mat image;
    cv::cvtColor(image2, image, CV_BGR2GRAY);
    result.create(image.size(), image.type());


    cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));
    
    kernel.at<float>(1, 1) = 5.0;
    kernel.at<float>(0, 1) = -1.0;
    kernel.at<float>(2, 1) = -1.0;
    kernel.at<float>(1, 0) = -1.0;
    kernel.at<float>(1, 2) = -1.0;

    //filter the image
    cv::filter2D(image, result, image.depth(), kernel);
}

 
ここではテンプレートを1つの画像と見なします(実際には負の値は意味がありません).フィルタリングについては様々な点があり、周波数領域フィルタリングまで復習するしかありません.

5 Simple image arithmetic


ここではopencv 2が提供するいくつかのマトリクス操作の関数です.
算術演算:cv::add()、cv::addWeighted()、cv::scaleAdd();cv::subtract, cv::absdiff; cv::multiply; cv::divideは、maskパラメータによって処理を必要としないビットをマスクすることもできる.
ビット演算:cv::bitwise_and, cv::bitwise_or, cv::bitwise_xor, cv::bitwise_not
    cv::max, cv::min
その他の演算:cv::sqrt,cv::pow,cv::abs,cv::cuberoot,cv::exp,cv::log
これらの関数は、マトリクスの各要素に対応して動作します.より便利なのは、マトリクスの加減乗除、bitwise operators....全部重荷されました.inv()求逆、t()求転置、determinant()求行列式、norm()求範数、cross()求両ベクトルのフォーク乗、dot()求両ベクトルの点乗.
1つのマルチチャネル画像を分離する必要がある場合、cv::split()メソッドを呼び出し、std::vectorで中間量を保存し、最後にcv::merge()メソッド合成を呼び出すことができます.コードは以下の通りです.
std::vector<cv::Mat> planes;
cv::split(image1, planes);
planes[0] += image2;
cv::merge(planes, result);

6 Region of interest


コードを直接付けましょう.
 
void addROI(cv::Mat& image, cv::Mat& logo)
{
    cv::Mat imageROI;
    imageROI = image(cv::Rect(385, 270, logo.cols, logo.rows));

    // 
    cv::addWeighted(imageROI, 1.0, logo, 3.0, 0., imageROI);
    // , logo image 
    cv::Mat mask = cv::imread("..\\images\\logo.bmp", 0);
    logo.copyTo(imageROI, mask);
}

 
ROIの取得にcv::Rectクラスを使用して、オフセット、サイズ属性などの長方形のボックスを表します.imageROIはもちろんin-placeの参照方式であり、入力画像の値が変わります.また,両方向のcv::Rangeを定義することによって実現され,いずれも差は少ない.オペレータ「()」はリロードされ、サブブロックが返されることは明らかです.また、cv::MatのrowRange()メソッドとcolRange()メソッドを呼び出すことで、行、列で指定することもできます.