如何检测强度梯度方向

4
拥有一个由灰度像素组成的正方形区域的Mat。如何创建一条直线,其方向是垂直于大多数像素值变化方向(平均梯度,整个Mat上的平均值)创建的(结果将只是一个方向(可以绘制为一条线))?
例如,有以下内容:

enter image description here

它会看起来像是:

enter image description here

在OpenCV中(使用python或C++)怎样做这件事?

听起来就像是普通的梯度... - Julien
我看到这张图片的像素几乎是均匀的。你指的是哪些像素? - Roger Figueroa Quintero
1
请定义“垂直于像素值”。 - Ripi2
对我来说也不清楚,您是在哪个方向上进行平均: 选项1: 如果您对整张图片进行平均,结果就只有一个方向(可以画成一条线) 选项2: 如果您在图片中对一条线进行平均,得到的是 N 个方向 (对应于一条波浪形的线,就像您所画的那样)。 选项3: 如果您对图片的一列进行平均 -> 类似于选项2 请让我们知道您想要什么。 - Markus Dutschke
3个回答

8
一份OpenCV实现看起来会像下面这样。它解决问题的方式与Mark Setchell所解释的答案类似,只是归一化图像对结果方向没有任何影响。
Mat img = imread("img.png", IMREAD_GRAYSCALE);

// compute the image derivatives for both the x and y direction
Mat dx, dy;
Sobel(img, dx, CV_32F, 1, 0);
Sobel(img, dy, CV_32F, 0, 1);

Scalar average_dx = mean(dx);
Scalar average_dy = mean(dy);

double average_gradient = atan2(-average_dy[0], average_dx[0]);
cout << "average_gradient = " << average_gradient << endl;

并且展示出结果方向

Point center = Point(img.cols/2, img.rows/2);
Point direction = Point(cos(average_gradient) * 100, -sin(average_gradient) * 100);

Mat img_rgb = imread("img.png"); // read the image in colour
line(img_rgb, center, center + direction, Scalar(0,0,255));
imshow("image", img_rgb);
waitKey();

image direction


2
干得好 - 做得好!我同意关于规范化的观点 - 我主要是为了可视化目的而这样做,因为原始问题上的一些评论暗示人们看不到 OP 所寻找的内容。 - Mark Setchell

5
我无法给你提供使用OpenCV的方法,但我可以告诉你这个方法,并使用ImageMagick在命令行演示。
首先,我认为你需要将图像转换为灰度图并将其标准化为完整的黑白范围 - 像这样:
convert gradient.png -colorspace gray -normalize stage1.png

enter image description here

接下来,您需要使用Sobel滤波器计算图像的X梯度和Y梯度,然后取Y梯度除以X梯度的反正切:

convert stage1.png -define convolve:scale='50%!' -bias 50% \
  \( -clone 0 -morphology Convolve Sobel:0 \) \
  \( -clone 0 -morphology Convolve Sobel:90 \) \
  -fx '0.5+atan2(v-0.5,0.5-u)/pi/2' result.jpg

那么,result.jpg中像素的平均值就是你的线的方向。

您可以像这样查看X和Y梯度卷积中使用的系数:

convert xc: -define morphology:showkernel=1 -morphology Convolve Sobel:0 null:

Kernel "Sobel" of size 3x3+1+1 with values from -2 to 2
Forming a output range from -4 to 4 (Zero-Summing)
 0:         1         0        -1
 1:         2         0        -2
 2:         1         0        -1


convert xc: -define morphology:showkernel=1 -morphology Convolve Sobel:90 null:
Kernel "Sobel@90" of size 3x3+1+1 with values from -2 to 2
Forming a output range from -4 to 4 (Zero-Summing)
 0:         1         2         1
 1:         0         0         0
 2:        -1        -2        -1

请查看维基百科此处,特别是这一行:

enter image description here


3
将图像转换为灰度图并根据灰度级别对其像素进行分类。对于分类,您可以使用类似Otsu方法或具有2个聚类的kmeans等算法。然后进行形态梯度以检测边界。
这是使用Otsu方法进行分类的像素和边界。

otsu boundaty

现在找到边界图像的非零像素,并使用fitLine函数对这些像素拟合2D线,该函数可以找到加权最小二乘线或使用this RANSAC实现。fitLine给出与该线共线的归一化向量。使用此向量,您可以找到一个垂直于它的正交向量。 使用以下代码,我得到了[0.983035,-0.183421]作为共线向量。因此,[0.183421 0.983035]是垂直于此向量的向量。 在左图中,红线是最小二乘线,蓝线是与红线垂直的线。在右图中,红线是最小二乘线,绿线是使用上述RANSAC库拟合的线。

lsq ransac

Mat im = imread("LP24W.png", 0);

Mat bw, gr;

threshold(im, bw, 0, 255, CV_THRESH_BINARY|CV_THRESH_OTSU);

Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
morphologyEx(bw, gr, CV_MOP_GRADIENT, kernel);

vector<vector<Point>> contours;
findContours(gr, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
vector<Point> points;
for (vector<Point>& cont: contours)
{
    points.insert(points.end(), cont.begin(), cont.end());
}
Vec4f line;
fitLine(points, line, CV_DIST_L2, 0, 0.01, 0.01);
cout << line << endl;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接