在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中所述。结构张量定义为:
其中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个通道?
答案 0 :(得分:1)
计算完导数后,进一步的处理将独立处理每个像素位置。这意味着我们可以利用像素数据在内存中的布局,并将其cv::reshape
变成更方便的形状。
我们可以将dx
和dy
变成1通道Mat
,具有3列(和宽*高行)-每个原始像素都位于新行上,首先列对应于L
频道,第二对应于a
,第三对应于b
。重塑是O(1)操作,因此此操作的成本可以忽略不计。
按元素乘法仍将以相同的方式工作(尽管我们可以使用matrix expression而不是multiply
来使事情更简洁)。
然后我们可以使用cv::reduce
来获取每行(即每像素)的总和。
最后,将E
,F
和G
重塑为原始宽度/高度。
示例代码
#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
优雅地计算结果。
与使用reshape
和reduce
的方法相比,最终的实现所需的时间大约少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()