Как эффективно настроить заданный канал cv:: Mat на заданное значение без изменения других каналов?

Как эффективно установить данный канал в cv::Mat для данного значения без изменения других каналов? Например, я хочу установить значение четвертого канала (альфа-канала) равным 120 (т.е. полупрозрачный), что-то вроде:

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

PS: Мое текущее решение - сначала разбить mat на несколько каналов и установить альфа-канал, а затем объединить их обратно.

PS2: я знаю, что могу сделать это быстро, если я также хочу изменить другие каналы:

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: Стоит отметить, что, согласно документации, матрицы, созданные с помощью Mat::create(), всегда непрерывны. Но если вы извлекаете часть матрицы с помощью Mat::col(), Mat::diag() и т.д. Или создаете заголовок матрицы для внешних данных, такие матрицы могут больше не иметь этого свойства.

Ответ 1

Если ваше изображение непрерывно в памяти, вы можете использовать следующий трюк:

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

Изменить (спасибо Антонио за комментарий):

Обратите внимание, что этот код может быть самым коротким и не выделяет новую память, но он неэффективен вообще. Это может быть еще медленнее, чем подход split/merge. 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 и одно меньшее значение в регистре. В системе с ограниченными ресурсами она может дать ~ 5% ускорение. В противном случае производительность будет одинаковой.

Ответ 2

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

Ответ 3

Как насчет прямого доступа 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;
}

Ответ 4

Простой алгоритм:

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