opencv-通过添加R + G + B

时间:2019-01-29 13:33:46

标签: opencv

在OpenCV中,是否有一种简单的方法可以将BGR图像转换为单通道图像,其中每个像素是B,G和R的总和?

例如以下矩阵:

[
  [1, 2, 3], [4, 5, 6],
  [10, 20, 30], [40, 50, 60]
]

转换为:

[
  [6], [15],
  [60], [150]
]

我知道我可以在3个通道的图像上使用split,然后将所有三个通道相加,但想知道是否还有更直接的方法。

[编辑]

我要做的是计算图像的结构张量,如this paper中所述。结构张量定义为:
1 其中Lx,ax和bx是每个L,a,b通道的sobel导数

我当前的实现方式(如果我正确理解了本文):

// Load and convert to Lab (keeping the raw lab values by using 32F matrix)
Mat lab;
img.convertTo(lab, CV_32F, 1 / 255.0);
cvtColor(lab, lab, COLOR_BGR2Lab);

// Compute derivatives in both directions 
Mat dx, dy;
Sobel(lab, dx, CV_32F, 1, 0, 3);
Sobel(lab, dy, CV_32F, 0, 1, 3);

// Now need to convert dx to single channel by adding L^2 + a^2 + b^2
Mat dx2;
multiply(dx, dx, dx2);
vector<Mat> dx2Cnls;
split(dx2, dx2Cnls);
Mat E = dx2Cnls[0] /* Lx^2 */ + dx2Cnls[1] /* ax^2 */ + dx2Cnls[2] /* bx^2 */;

// Do the same for dy
Mat dy2;
multiply(dy, dy, dy2);
vector<Mat> dy2Cnls;
split(dy2, dy2Cnls);
Mat G = dy2Cnls[0] /* Ly^2 */ + dy2Cnls[1] /* ay^2 */ + dy2Cnls[2] /* by^2 */;

// And now the cross dx * dy
Mat dxdy;
multiply(dx, dy, dxdy);
vector<Mat> dxdyCnls;
split(dxdy, dxdyCnls);
Mat F = dxdyCnls[0] /* LxLy */ + dxdyCnls[1] /* axay */ + dxdyCnls[2] /* bxby */;

有什么方法可以避免使用split来组合3个通道?

2 个答案:

答案 0 :(得分:1)

计算完导数后,进一步的处理将独立处理每个像素位置。这意味着我们可以利用像素数据在内存中的布局,并将其cv::reshape变成更方便的形状。

我们可以将dxdy变成1通道Mat,具有3列(和宽*高行)-每个原始像素都位于新行上,首先列对应于L频道,第二对应于a,第三对应于b。重塑是O(1)操作,因此此操作的成本可以忽略不计。

按元素乘法仍将以相同的方式工作(尽管我们可以使用matrix expression而不是multiply来使事情更简洁)。

然后我们可以使用cv::reduce来获取每行(即每像素)的总和。

最后,将EFG重塑为原始宽度/高度。


示例代码

#include <opencv2/opencv.hpp>

int main()
{
    // Make some random data
    cv::Mat img(16, 16, CV_8UC3);
    cv::randu(img, 0, 256);

    // Load and convert to Lab (keeping the raw lab values by using 32F matrix)
    cv::Mat lab;
    img.convertTo(lab, CV_32F, 1 / 255.0);
    cv::cvtColor(lab, lab, cv::COLOR_BGR2Lab);

    // Compute derivatives in both directions 
    cv::Mat dx, dy;
    cv::Sobel(lab, dx, CV_32F, 1, 0, 3);
    cv::Sobel(lab, dy, CV_32F, 0, 1, 3);

    // Reshape the arrays to 1-channel, such that each original pixel is on
    // one row, first column is L, second is a, third is b
    // NB: This is O(1) operation.
    int new_row_count(lab.rows * lab.cols);
    dx = dx.reshape(1, new_row_count);
    dy = dy.reshape(1, new_row_count);

    // Get per-row (i.e. per pixel) sums for dx*dx, dx*dy, and dy*dy
    cv::Mat E, F, G;
    cv::reduce(dx.mul(dx), E, 1, cv::REDUCE_SUM);
    cv::reduce(dx.mul(dy), F, 1, cv::REDUCE_SUM);
    cv::reduce(dy.mul(dy), G, 1, cv::REDUCE_SUM);

    // Return back to original shape (but now only single channel)
    E = E.reshape(1, lab.rows);
    F = F.reshape(1, lab.rows);
    G = G.reshape(1, lab.rows);

    return 0;
}

万一我们追求表现,还有一些机会。

具体来说,如果我们同时计算E[r,c]F[r,c]G[r,c]

  • 我们可以避免分配临时Mat来容纳导数的乘积(例如dx.mul(dx) -即使我们不将矩阵表达式的结果存储在变量中,它仍会分配一个临时Mat传递给reduce)。

  • 我们可以使算法更易于缓存-我们只读取一次dx[r,c]dy[r,c]一次,并避免将产品存储到内存中并再次检索它们(通过这种方式时间可能已经从缓存中将它们驱逐了。)

为了允许使用不连续的输出数组进行处理(可能性很小,但成本最低),我们将使用两个循环进行迭代。外循环将遍历行,并且对于每一行将检索所有必要的输入和输出指针。内部循环将遍历各列,并根据E[r,c]F[r,c]计算G[r,c]dx[r,c]dy[r,c]

如果我们看一下公式...

E = Lx^2 + ax^2 + bx^2

E[r,c] = dx[r,c,0] * dx[r,c,0] + dx[r,c,1] * dx[r,c,1] + dx[r,c,2] * dx[r,c,2]

我们可以看到这基本上是dot product

E[r,c] = dx[r,c] · dx[r,c]

因此,我们可以使用cv::Vec3f::dot优雅地计算结果。

与使用reshapereduce的方法相比,最终的实现所需的时间大约少40%。

示例代码


#include <opencv2/opencv.hpp>

void structure_tensor(cv::Mat const& img, cv::Mat& E, cv::Mat& F, cv::Mat& G)
{
    // Load and convert to Lab (keeping the raw lab values by using 32F matrix)
    cv::Mat lab;
    img.convertTo(lab, CV_32F, 1 / 255.0);
    cv::cvtColor(lab, lab, cv::COLOR_BGR2Lab);

    // Compute derivatives in both directions 
    cv::Mat dx, dy;
    cv::Sobel(lab, dx, CV_32F, 1, 0, 3);
    cv::Sobel(lab, dy, CV_32F, 0, 1, 3);

    E.create(lab.size(), CV_32FC1);
    F.create(lab.size(), CV_32FC1);
    G.create(lab.size(), CV_32FC1);

    for (int r(0); r < lab.rows; ++r) {
        cv::Vec3f const* idx = dx.ptr<cv::Vec3f>(r);
        cv::Vec3f const* idy = dy.ptr<cv::Vec3f>(r);
        float* iE = E.ptr<float>(r);
        float* iF = F.ptr<float>(r);
        float* iG = G.ptr<float>(r);

        for (int c(0); c < lab.cols; ++c, ++idx, ++idy) {
            *iE++ = idx->dot(*idx);
            *iF++ = idx->dot(*idy);
            *iG++ = idy->dot(*idy);
        }

        /* Or like this, pick what you like more.
        for (int c(0); c < lab.cols; ++c) {
            iE[c] = idx[c].dot(idx[c]);
            iF[c] = idx[c].dot(idy[c]);
            iG[c] = idy[c].dot(idy[c]);
        }
        */
    }
}

int main()
{
    // Make some random data
    cv::Mat img(16, 16, CV_8UC3);
    cv::randu(img, 0, 256);

    cv::Mat E, F, G;

    structure_tensor(img, E, F, G);

    return 0;
}

答案 1 :(得分:0)

对于C ++,您可以使用forEach成员:

retryLoadingOnErrorIntent()