如何解决OpenCV - 在视频和图像上找到黑板边缘
更新
您可以在此处找到我在 GitHub 上用于测试的所有图像:
GitHub repository with sources
还有 2 个视频,检测也应该在其中进行
原始问题
我尝试使用 OpenCV 4.x.x 来查找黑板的边缘(下图),但不知何故我无法成功。我目前的代码如下所示:(带有 OpenCV 和实时摄像头源的 Android),其中 imgMat 是来自摄像头源的 Mat:
Mat gray = new Mat();
Imgproc.cvtColor(imgMat,gray,Imgproc.COLOR_RGB2BGR);
Mat blurred = new Mat();
Imgproc.blur(gray,blurred,new org.opencv.core.Size(3,3));
Mat canny = new Mat();
Imgproc.Canny(blurred,canny,80,230);
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT,new org.opencv.core.Size(2,2));
Mat dilated = new Mat();
Imgproc.morphologyEx(canny,dilated,Imgproc.MORPH_DILATE,kernel,new Point(0,0),10);
Mat rectimage = new Mat();
Imgproc.morphologyEx(dilated,rectimage,Imgproc.MORPH_CLOSE,5);
Mat endproduct = new Mat();
Imgproc.Canny(rectimage,endproduct,120,230);
List<MatOfPoint> contours = new ArrayList<>();
Mat hierarchy = new Mat();
Imgproc.findContours(endproduct,contours,hierarchy,Imgproc.RETR_LIST,Imgproc.CHAIN_APPROX_SIMPLE);
double maxArea = 0;
boolean hasContour = false;
MatOfPoint2f biggestContour = new MatOfPoint2f();
Iterator<MatOfPoint> each = contours.iterator();
while (each.hasNext()) {
MatOfPoint wrapper = each.next();
double area = Imgproc.contourArea(wrapper);
if (area > maxArea) {
maxArea = area;
biggestContour = new MatOfPoint2f(wrapper.toArray());
hasContour = true;
}
}
if (hasContour) {
Mat output = imgMat.clone();
MatOfPoint2f approx = new MatOfPoint2f();
MatOfPoint poly = new MatOfPoint();
Imgproc.approxpolyDP(biggestContour,approx,Imgproc.arcLength(biggestContour,true) * .02,true);
approx.convertTo(poly,CvType.CV_32S);
Rect rect = Imgproc.boundingRect(poly);
}
不知何故,我无法让它工作,尽管相同的代码(用 python 编写)在我的计算机上运行并带有视频。我从矩形中获取输出并将其显示在我的移动屏幕上,它在那里闪烁很多并且无法正常工作。
这些是我在 python 程序上尝试过的图像,它们起作用了:
我做错了什么?我无法不断检测黑板的边缘。
有关黑板的其他信息:
- 总是矩形
- 可能有不同的照明
- 应该忽略文本,只检测主板
- 外面的黑板也应该被忽略
- 只应显示/返回主板的轮廓
感谢您的任何建议或代码!
解决方法
我使用 HSV 是因为这是检测特定颜色的最简单方法。我使用了丰度测试来自动选择颜色阈值(因此这适用于绿色或蓝色板)。但是,此测试在白色或黑色板上会失败,因为根据色调,白色和黑色都算作所有颜色。相反,在 HSV 中,白色和黑色最容易被检测为非常低的饱和度(白色)或非常低的值(黑色)。
我对每个都进行了 3 向检查,并选择了其中像素最多的蒙版(我假设板是图像的主要部分)。我不确定这将如何在其他图像上工作,因为我们这里只有一个,所以这可能适用于其他主板,也可能不适用。
我使用 approxPolyDP 来减少轮廓中的点数,直到我有 4 个点并用它来绘制形状。
import cv2
import numpy as np
# get unique colors (to speed up search) and return the most abundant mask
def getAbundantColor(channel,margin):
# get uniques
unique_colors,counts = np.unique(channel,return_counts=True);
# check for the most abundant color
most = None;
biggest_count = -1;
for col in unique_colors:
# count number of white pixels
mask = cv2.inRange(channel,int(col - margin),int(col + margin));
count = np.count_nonzero(mask);
# if bigger,set new "most"
if count > biggest_count:
biggest_count = count;
most = mask;
return most,biggest_count;
# load image
img = cv2.imread("blackboard.jpg");
# it's huge,scale down so that we can see the whole thing
h,w = img.shape[:2];
scale = 0.25;
h = int(scale*h);
w = int(scale*w);
img = cv2.resize(img,(w,h));
# hsv
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV);
h,s,v = cv2.split(hsv);
# median blur to get rid of most of the text
h = cv2.medianBlur(h,5);
s = cv2.medianBlur(s,5);
v = cv2.medianBlur(v,5);
# get most abundant color
color_margin = 30;
hmask,hcount = getAbundantColor(h,color_margin);
# detect white and black separately
light_margin = 30;
# white
wmask = cv2.inRange(s,light_margin);
wcount = np.count_nonzero(wmask);
# black
bmask = cv2.inRange(v,light_margin);
bcount = np.count_nonzero(bmask);
# check which is biggest
sorter = [[hcount,hmask],[wcount,wmask],[bcount,bmask]];
sorter.sort();
mask = sorter[-1][1];
# dilate and erode to close holes
kernel = np.ones((3,3),np.uint8);
mask = cv2.dilate(mask,kernel,iterations = 2);
mask = cv2.erode(mask,iterations = 4);
mask = cv2.dilate(mask,iterations = 2);
# get contours # OpenCV 3.4,in OpenCV 2* or 4* it returns (contours,_)
_,contours,_ = cv2.findContours(mask,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE);
# for each contour,approximate a simpler shape until we have 4 points
simplified = [];
for con in contours:
# go until we have 4 points
num_points = 999999;
step_size = 0.01;
percent = step_size;
while num_points >= 4:
# get number of points
epsilon = percent * cv2.arcLength(con,True);
approx = cv2.approxPolyDP(con,epsilon,True);
num_points = len(approx);
# increment
percent += step_size;
# step back and get the points
# there could be more than 4 points if our step size misses it
percent -= step_size * 2;
epsilon = percent * cv2.arcLength(con,True);
approx = cv2.approxPolyDP(con,True);
simplified.append(approx);
cv2.drawContours(img,simplified,-1,(0,200),2);
# print out the number of points
for points in simplified:
print("Num Points: " + str(len(points)));
# show image
cv2.imshow("Image",img);
cv2.imshow("Hue",h);
cv2.imshow("Mask",mask);
cv2.waitKey(0);
编辑:为了适应电路板颜色和外观的不确定性,我假设电路板本身将是图片的主要部分。涉及分拣机的线正在寻找图像中最丰富的颜色。如果板子后面的白墙在图像中占据更多空间,那么这将是为遮罩选择的颜色。
还有其他方法可以尝试只选择棋盘,但是想出一个包罗万象的解决方案真的很困难。如果您能想出某种方法来屏蔽电路板,其余的代码应该做同样的工作。如果您愿意在未知颜色假设上让步并提供失败案例的原始图片,那么我可能会想出一个合适的掩码。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。