我正在处理栅格图像绘制,所以我的目标是仅检测门形状
我正在使用 Emgu C# 并应用了 Haris Corner 算法,阈值= 50
然后检测拐角矩阵,然后计算两点之间的距离,以近似于两点是门形状的起点和终点
问题:
我无法过滤图像以进行最佳检测,例如如何删除所有文本,而噪点只能保留粗体的墙壁
[![在此处输入图片描述] [1]] [1]
[![在此处输入图片描述] [2]] [2]
var img = imgList["Input"].Clone();
var gray = img.Convert<Gray, byte>().ThresholdBinaryInv(new Gray(100), new Gray(100)); ;
imageBoxEx2.Image = gray.ToBitmap();
var corners = new Mat();
CvInvoke.CornerHarris(gray, corners,2);
CvInvoke.Normalize(corners, corners, 255, 0, Emgu.CV.CvEnum.NormType.MinMax);
Matrix<float> matrix = new Matrix<float>(corners.Rows, corners.Cols);
corners.CopyTo(matrix);
dt.Rows.Clear();
List<Point> LstXpoints = new List<Point>();
List<Point> LstYpoints = new List<Point>();
List<PointF> LstF = new List<PointF>();
for (int i = 0; i < matrix.Rows; i++)
{
for (int j = 0; j < matrix.Cols; j++)
{
if (matrix[i, j] > threshold)
{
LstXpoints.Add(new Point ( j, i));
LstYpoints.Add(new Point(i, j));
// CvInvoke.Circle(img, new Point(j, i), 5, new MCvScalar(0, 0, 255), 3);
}
}
}
答案 0 :(得分:3)
[编辑-扩展答案以提供完整的解决方案]
我通常不会提供“解决方案”,因为我觉得它远远超出了有用的,可重用的问答格式……但这是一个有趣的问题。
以下详细介绍了一种基本算法,用于检测平面图中的潜在门。除了所提供的单个案例之外,还没有对性能进行优化或测试。由于门的定义仅由OP给出为“指定宽度的开口”,因此也容易出现错误指示。该算法只能检测正交的门原理。
示例结果:
方法如下:
// Open the image
Image<Gray, byte> baseImage = new Image<Gray, byte>(@"TestLayout.jpg");
// Invert the image
Image<Gray, byte> invBaseImage = baseImage.Not();
// Threshold the image so as "close to white" is maintained, all else is black
Image<Gray, byte> blackOnlyImage = invBaseImage.ThresholdBinary(new Gray(200), new Gray(255));
// An output image of the same size to contain the walls
Image<Gray, byte> wallsOnlyImage = new Image<Gray, byte>(blackOnlyImage.Size);
// A set of dected contours
VectorOfVectorOfPoint inputContours = new VectorOfVectorOfPoint();
// A set of validated contours
List<VectorOfPoint> validContours = new List<VectorOfPoint>();
// Perform contour detection
Mat hierarchy = new Mat();
CvInvoke.FindContours(blackOnlyImage, inputContours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple);
// Filter out to select only contours bounding more that 500 pixels
int areaThreshold = 500;
for (int c = 0; c < inputContours.Size; c++)
{
if (CvInvoke.ContourArea(inputContours[c]) >= areaThreshold)
{
validContours.Add(inputContours[c]);
}
}
// Find all the corner points in the valid contours
List<Point> contourCorners = new List<Point>();
foreach(VectorOfPoint contour in validContours)
{
contourCorners.AddRange(CornerWalk(contour, 80));
}
// Sort the contour corners by proximity to origin in order to optimise following loops
contourCorners.OrderBy(p => Math.Sqrt(Math.Pow(p.X, 2) + Math.Pow(p.Y, 2)));
// Extract all door candidate point pairs from all detected corners
List<Tuple<Point, Point>> doorCandidates = FindDoors(contourCorners, 2, 30, 45);
// Pixels contained within the filtered contours are walls, fill them white
RasterFill(wallsOnlyImage, validContours);
// Output Image
Image<Rgb, byte> outputImage = new Image<Rgb, byte>(wallsOnlyImage.Size);
CvInvoke.CvtColor(wallsOnlyImage, outputImage, ColorConversion.Gray2Rgb);
// Draw the doors
foreach (Tuple<Point,Point> door in doorCandidates)
{
outputImage.Draw(new LineSegment2D(door.Item1, door.Item2), new Rgb(255,0,0), 1);
}
// Display generated output and save it to file
CvInvoke.NamedWindow("TestOutput");
CvInvoke.Imshow("TestOutput", outputImage);
CvInvoke.WaitKey();
outputImage.Save(@"OutputImage.bmp");
static List<Point> CornerWalk(VectorOfPoint contour, int threshold)
{
// Create a resultant list of points
List<Point> result = new List<Point>();
// Points are used to store 2D vectors as dx,dy (i,j)
Point reverseVector, forwardVector;
double theta;
// For each point on the contour
for(int p = 1; p < contour.Size; p++)
{
// Determine the vector to the prior point
reverseVector = new Point()
{
X = contour[p].X - contour[p - 1].X,
Y = contour[p].Y - contour[p - 1].Y,
};
// Determine the vector to the next point
forwardVector = p == contour.Size - 1 ?
new Point()
{
X = contour[0].X - contour[p].X,
Y = contour[0].Y - contour[p].Y,
} :
new Point()
{
X = contour[p + 1].X - contour[p].X,
Y = contour[p + 1].Y - contour[p].Y,
};
// Compute the angular delta between the two vectors (Radians)
theta = Math.Acos(((reverseVector.X * forwardVector.X) + (reverseVector.Y * forwardVector.Y)) /
(Math.Sqrt(Math.Pow(reverseVector.X, 2) + Math.Pow(reverseVector.Y, 2)) *
Math.Sqrt(Math.Pow(forwardVector.X, 2) + Math.Pow(forwardVector.Y, 2))));
// Convert the angle to degrees
theta *= 180 / Math.PI;
// If the angle is above or equal the threshold, the point is a corner
if (theta >= threshold) result.Add(contour[p]);
}
// Return the result
return result;
}
static List<Tuple<Point, Point>> FindDoors(
List<Point> cornerPoints,
int inLineTolerance,
int minDoorWidth,
int maxDoorWidth)
{
// Create a resultant list of pairs of points
List<Tuple<Point, Point>> results = new List<Tuple<Point, Point>>();
Point p1, p2;
// For every point in the list
for (int a = 0; a < cornerPoints.Count; a++)
{
p1 = cornerPoints[a];
// Against every other point in the list
for (int b = 0; b < cornerPoints.Count; b++)
{
// Don't compare a point to it's self...
if (a == b) continue;
p2 = cornerPoints[b];
// If p1 to p2 qualifies as a door:
// Vertical Doors - A vertical door will have to points of the same X value, within tolerance, and a Y value delta within the
// min-max limits of a door width.
if (((Math.Abs(p1.X - p2.X) < inLineTolerance) && (Math.Abs(p1.Y - p2.Y) > minDoorWidth) && (Math.Abs(p1.Y - p2.Y) < maxDoorWidth)) ||
// Horizontal Doors - A horizontal door will have to points of the same Y value, within tolerance, and a X value delta within the
// min-max limits of a door width.
((Math.Abs(p1.Y - p2.Y) < inLineTolerance) && (Math.Abs(p1.X - p2.X) > minDoorWidth) && (Math.Abs(p1.X - p2.X) < maxDoorWidth)))
{
// Add the point pair to the result
results.Add(new Tuple<Point, Point>(p1, p2));
// Remove them from further consideration
cornerPoints.Remove(p1);
cornerPoints.Remove(p2);
// Decrement the looping indexes and start over with a new p1
b--; a--;
break;
}
}
}
// Finally return the result
return results;
}
static void RasterFill(Image<Gray,byte> dstImg, List<VectorOfPoint> contours)
{
Rectangle contourBounds;
PointF testPoint;
// For each contour detected
foreach(VectorOfPoint contour in contours)
{
// Within the bounds of this contour
contourBounds = CvInvoke.BoundingRectangle(contour);
for (int u = contourBounds.X; u < contourBounds.X + contourBounds.Width; u++)
{
for (int v = contourBounds.Y; v < contourBounds.Y + contourBounds.Height; v++)
{
// Test to determine whether the point is within the contour
testPoint = new PointF(u, v);
// If it is inside the contour, OR on the contour
if (CvInvoke.PointPolygonTest(contour, testPoint, false) >= 0)
{
// Set it white
dstImg.Data[v, u, 0] = 255;
}
}
}
}
}
答案 1 :(得分:1)
对不起,python代码。但这也许会帮助您解决问题。 查看评论。
import cv2
img = cv2.imread('NHoXn.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# convert to binary image
thresh=cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY )[1]
# Morphological reconstruction (delete labels)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7,7))
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
marker = cv2.dilate(thresh,kernel,iterations = 1)
while True:
tmp=marker.copy()
marker=cv2.erode(marker, kernel2)
marker=cv2.max(thresh, marker)
difference = cv2.subtract(tmp, marker)
if cv2.countNonZero(difference) == 0:
break
# only walls
se=cv2.getStructuringElement(cv2.MORPH_RECT, (4,4))
walls=cv2.morphologyEx(marker, cv2.MORPH_CLOSE, se)
walls=cv2.erode(walls, kernel2,iterations=2)
# other objects
other=cv2.compare(marker,walls, cv2.CMP_GE)
other=cv2.bitwise_not(other)
# find connected components and select by size and area
output = cv2.connectedComponentsWithStats(other, 4, cv2.CV_32S)
num_labels = output[0]
labels = output[1]
stats=output[2]
centroids = output[3]
for i in range(num_labels):
left,top,width,height,area=stats[i]
if abs(width-40)<12 and abs(height-40)<12 and area>85:
cv2.rectangle(img,(left, top), (left+width, top+height), (0,255,0))
cv2.imwrite('doors.png', img)