2021 年 11 月 25 日更新
- 更新代码示例
- 修复了图像边框的错误
- 添加更多图片
- 添加支持 CMake 的 Github 存储库以构建示例应用程序
可在此处找到完整的开箱即用示例:
C++ application with CMake
一般信息
- 我正在使用 OpenCV 3.0.0
- 使用
cv::findContours 实际上会更改输入图像,因此请确保您在专门为此功能的单独副本上工作,或者根本不进一步使用该图像
2019-03-07 更新:“因为 opencv 3.2 源图像没有被这个函数修改。”(见corresponding OpenCV documentation)
一般解决方案
您只需要知道轮廓是否有任何点触及图像边框。可以通过以下两个过程之一轻松提取此信息:
- 检查轮廓的每个点的位置。如果它位于图像边界(x = 0 或 x = 宽度 - 1 或 y = 0 或 y = 高度 - 1),请忽略它。
- 围绕轮廓创建一个边界框。如果边界框位于图像边框上,您就知道轮廓也是如此。
第二种解决方案(CMake)的代码:
cmake_minimum_required(VERSION 2.8)
project(SolutionName)
find_package(OpenCV REQUIRED)
set(TARGETNAME "ProjectName")
add_executable(${TARGETNAME} ./src/main.cpp)
include_directories(${CMAKE_CURRENT_BINARY_DIR} ${OpenCV_INCLUDE_DIRS} ${OpenCV2_INCLUDE_DIR})
target_link_libraries(${TARGETNAME} ${OpenCV_LIBS})
第二种解决方案的代码(C++):
bool contourTouchesImageBorder(const std::vector<cv::Point>& contour, const cv::Size& imageSize)
{
cv::Rect bb = cv::boundingRect(contour);
bool retval = false;
int xMin, xMax, yMin, yMax;
xMin = 0;
yMin = 0;
xMax = imageSize.width - 1;
yMax = imageSize.height - 1;
// Use less/greater comparisons to potentially support contours outside of
// image coordinates, possible future workarounds with cv::copyMakeBorder where
// contour coordinates may be shifted and just to be safe.
// However note that bounding boxes of size 1 will have their start point
// included (of course) but also their and with/height values set to 1
// but should not contain 2 pixels.
// Which is why we have to -1 the "search grid"
int bbxEnd = bb.x + bb.width - 1;
int bbyEnd = bb.y + bb.height - 1;
if (bb.x <= xMin ||
bb.y <= yMin ||
bbxEnd >= xMax ||
bbyEnd >= yMax)
{
retval = true;
}
return retval;
}
通过以下方式调用它:
...
cv::Size imageSize = processedImage.size();
for (auto c: contours)
{
if(contourTouchesImageBorder(c, imageSize))
{
// Do your thing...
int asdf = 0;
}
}
...
完整的 C++ 示例:
void testContourBorderCheck()
{
std::vector<std::string> filenames =
{
"0_single_pixel_top_left.png",
"1_left_no_touch.png",
"1_left_touch.png",
"2_right_no_touch.png",
"2_right_touch.png",
"3_top_no_touch.png",
"3_top_touch.png",
"4_bot_no_touch.png",
"4_bot_touch.png"
};
// Load example image
//std::string path = "C:/Temp/!Testdata/ContourBorderDetection/test_1/";
std::string path = "../Testdata/ContourBorderDetection/test_1/";
for (int i = 0; i < filenames.size(); ++i)
{
//std::string filename = "circle3BorderDistance0.png";
std::string filename = filenames.at(i);
std::string fqn = path + filename;
cv::Mat img = cv::imread(fqn, cv::IMREAD_GRAYSCALE);
cv::Mat processedImage;
img.copyTo(processedImage);
// Create copy for contour extraction since cv::findContours alters the input image
cv::Mat workingCopyForContourExtraction;
processedImage.copyTo(workingCopyForContourExtraction);
std::vector<std::vector<cv::Point>> contours;
// Extract contours
cv::findContours(workingCopyForContourExtraction, contours, cv::RetrievalModes::RETR_EXTERNAL, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE);
// Prepare image for contour drawing
cv::Mat drawing;
processedImage.copyTo(drawing);
cv::cvtColor(drawing, drawing, cv::COLOR_GRAY2BGR);
// Draw contours
cv::drawContours(drawing, contours, -1, cv::Scalar(255, 255, 0), 1);
//cv::imwrite(path + "processedImage.png", processedImage);
//cv::imwrite(path + "workingCopyForContourExtraction.png", workingCopyForContourExtraction);
//cv::imwrite(path + "drawing.png", drawing);
const auto imageSize = img.size();
bool liesOnBorder = contourTouchesImageBorder(contours.at(0), imageSize);
// std::cout << "lies on border: " << std::to_string(liesOnBorder);
std::cout << filename << " lies on border: "
<< liesOnBorder;
std::cout << std::endl;
std::cout << std::endl;
cv::imshow("processedImage", processedImage);
cv::imshow("workingCopyForContourExtraction", workingCopyForContourExtraction);
cv::imshow("drawing", drawing);
cv::waitKey();
//cv::Size imageSize = workingCopyForContourExtraction.size();
for (auto c : contours)
{
if (contourTouchesImageBorder(c, imageSize))
{
// Do your thing...
int asdf = 0;
}
}
for (auto c : contours)
{
if (contourTouchesImageBorder(c, imageSize))
{
// Do your thing...
int asdf = 0;
}
}
}
}
int main(int argc, char** argv)
{
testContourBorderCheck();
return 0;
}
图像边界附近的轮廓检测问题
OpenCV 似乎在正确找到图像边界附近的轮廓方面存在问题。
对于两个对象,检测到的轮廓是相同的(见图)。但是,在图像 2 中,检测到的轮廓不正确,因为对象的一部分位于 x = 0 处,但轮廓位于 x = 1 处。
这对我来说似乎是一个错误。
这里有一个未解决的问题:https://github.com/opencv/opencv/pull/7516
似乎也有使用 cv::copyMakeBorder (https://github.com/opencv/opencv/issues/4374) 的解决方法,但它似乎有点复杂。
如果您能有点耐心,我建议您等待 OpenCV 3.2 的发布,这应该会在接下来的 1-2 个月内发布。
新的示例图片:
左上单像素,对象左、右、上、下,每个触摸和不触摸(1px 距离)
示例图片
- 对象触摸图像边框
- 对象未触及图像边框
- 对象触摸图像边框的轮廓
- 对象不接触图像边框的轮廓