web-dev-qa-db-ja.com

cv :: Matの特定のチャンネルを、他のチャンネルを変更せずに効率的に特定の値に設定するにはどうすればよいですか?

_cv::Mat_の特定のチャネルを他のチャネルを変更せずに特定の値に効率的に設定するにはどうすればよいですか?たとえば、4番目のチャネル(アルファチャネル)の値を_120_(つまり、半分透明)に設定したいのですが、次のようになります。

_cv::Mat mat; // with type CV_BGRA
...
mat.getChannel(3) = Scalar(120); // <- this is what I want to do
_

追伸:私の現在の解決策は、最初にmatを複数のチャネルに分割し、アルファチャネルを設定してから、それらをマージして戻すことです。

P.S.2:他のチャンネルも変更したい場合は、次の方法でこれをすばやく実行できることを知っています。

_mat.setTo(Scalar(54, 154, 65, 120)); 
_

一般化されたソリューションで更新します。

どちらの方法も、特定のチャンネルのすべてのマット値を特定の値に設定するために機能します。また、連続であるかどうかに関係なく、すべての行列で機能します。

方法-1-より効率的

-> @Antonioの回答に基づき、@ MichaelBurdinovによってさらに改善されました

_// set all mat values at given channel to given value
void setChannel(Mat &mat, unsigned int channel, unsigned char value)
{
    // make sure have enough channels
    if (mat.channels() < channel + 1)
        return;

    const int cols = mat.cols;
    const int step = mat.channels();
    const int rows = mat.rows;
    for (int y = 0; y < rows; y++) {
        // get pointer to the first byte to be changed in this row
        unsigned char *p_row = mat.ptr(y) + channel; 
        unsigned char *row_end = p_row + cols*step;
        for (; p_row != row_end; p_row += step)
            *p_row = value;
    }
}
_

方法-2-よりエレガント

-> @ MichaelBurdinovの回答に基づく

_// set all mat values at given channel to given value
void setChannel(Mat &mat, unsigned int channel, unsigned char value)
{
    // make sure have enough channels
    if (mat.channels() < channel+1)
        return;

    // check mat is continuous or not
    if (mat.isContinuous())
        mat.reshape(1, mat.rows*mat.cols).col(channel).setTo(Scalar(value));
    else{
        for (int i = 0; i < mat.rows; i++)
            mat.row(i).reshape(1, mat.cols).col(channel).setTo(Scalar(value));
    }
}
_

PS:documentation によると、Mat::create()で作成された行列は常に連続であることに注意してください。 。ただし、Mat::col()Mat::diag()などを使用して行列の一部を抽出したり、外部に割り当てられたデータの行列ヘッダーを作成したりすると、そのような行列にはこのプロパティがなくなる可能性があります。

15
herohuyongtao

画像がメモリ内で連続している場合は、次のトリックを使用できます。

mat.reshape(1,mat.rows*mat.cols).col(3).setTo(Scalar(120));

連続していない場合:

for(int i=0; i<mat.rows; i++)
    mat.row(i).reshape(1,mat.cols).col(3).setTo(Scalar(120));

編集(コメントを寄せてくれたAntonioに感謝):

このコードは最短である可能性があり、新しいメモリを割り当てていませんが、まったく効率的ではないことに注意してください。分割/マージアプローチよりもさらに遅い場合があります。 OpenCVは、1ピクセル連続の非連続行列に対して操作を実行する必要がある場合、非常に非効率的です。時間のパフォーマンスが重要な場合は、@ Antonioによって提案されたソリューションを使用する必要があります。

彼のソリューションのほんのわずかな改善:

const int cols = img.cols;
const int step = img.channels();
const int rows = img.rows;
for (int y = 0; y < rows; y++) {
    unsigned char* p_row = img.ptr(y) + SELECTED_CHANNEL_NUMBER; //gets pointer to the first byte to be changed in this row, SELECTED_CHANNEL_NUMBER is 3 for alpha
    unsigned char* row_end = p_row + cols*step;
    for(; p_row != row_end; p_row += step)
         *p_row = value;
    }
}

これにより、xのインクリメント操作が節約され、レジスタの値が1つ少なくなります。リソースが限られているシステムでは、最大5%のスピードアップが得られる可能性があります。それ以外の場合、時間パフォーマンスは同じになります。

12
Mat img;
[...]
const int cols = img.cols;
const int step = img.channels();
const int rows = img.rows;
for (int y = 0; y < rows; y++) {
    unsigned char* p_row = img.ptr(y) + SELECTED_CHANNEL_NUMBER; //gets pointer to the first byte to be changed in this row, SELECTED_CHANNEL_NUMBER is 3 for alpha
    for (int x = 0; x < cols; x++) {
         *p_row = value;
         p_row += step; //Goes to the next byte to be changed
    }
}

注:これは、opencvの用語の使用に従って、連続行列と非連続行列の両方で機能します: http://docs.opencv .org/modules/core/doc/basic_structures.html#bool%20Mat :: isContinuous%28%29%20const

7
Antonio

直接Mat :: dataアクセスについてはどうですか(setTo()または他のopencv Mat apiが同様のソリューションを使用すると確信しています):

template<int N>
void SetChannel(Mat &img, unsigned char newVal) {   
    for(int x=0;x<img.cols;x++) {
        for(int y=0;y<img.rows;y++) {
            *(img.data + (y * img.cols + x) * img.channels() + N) = newVal;
        }
    }
}


int main() {
    Mat img = Mat::zeros(1000, 1000, CV_8UC4);
    SetChannel<3>(img, 120);
    imwrite("out.jpg", img);

    return 0;
}
2
marol

単純なアルゴリズム:

void SetChannel(Mat mat, uint channel, uchar value)
{
    const uint channels = mat.channels();
    if (channel > channels - 1)
        return;

    uchar * data = mat.data;
    uint N = mat.rows * mat.step / mat.elemSize1();

    for (uint i = channel; i < N; i += channels)
        data[i] = value;
}