Как установить данный канал cv::Mat
к заданному значению эффективно, не меняя другие каналы? Например, я хочу установить значение четвертого канала (альфа-канала) в 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));
Оба метода будут работать для установки всех значений мата в данном канале на заданное значение. И они будут работать для всех матриц, являются ли они непрерывными или нет.
-> основано на ответе @ 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;
}
}
-> на основании ответа @ 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));
}
}
P.S .: Стоит отметить, что, согласно документация, матрицы, созданные с Mat::create()
всегда непрерывны. Но если вы извлекаете часть матрицы, используя Mat::col()
, Mat::diag()
и т. д., или сконструирован заголовок матрицы для внешних данных, такие матрицы могут больше не иметь этого свойства.
Если ваше изображение непрерывно в памяти, вы можете использовать следующий трюк:
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));
Изменить (спасибо Антонио за комментарий):
Обратите внимание, что этот код может быть самым коротким, и он не выделяет новую память, но он вообще неэффективен. Это может быть даже медленнее, чем разделение / слияние. 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% ускорения. В противном случае время выполнения будет таким же.
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
А как насчет прямого доступа 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;
}