我的图像是中间的文件以及周围的一些(地毯,桌子的桌子等)
我想删除所有周围环境,只提供一份文件。 我尝试了阈值和adaptiveThreshold,但我没有取得多大成就。
所以我想采取周围的样本,并根据所有周围的情况删除。
这在OpenCV中是否可行?
答案 0 :(得分:6)
使用CV :: ContourApproximationModes :: CHAIN_APPROX_NONE
或其区域 (只绘制填充的轮廓并计算绘制的像素 - 这是提取区域信息的最可靠方法)。
有多种方法可以解决这个问题:
阈值,应用形态学,轮廓搜索,选择最大轮廓。不需要包含背景的小图像(除了初始参数化之外)。
噪声检测(滤波和未滤波图像之间的简单差异,标准差的每像素邻域分析等 - 定义最大噪声阈值,然后应用cv :: threshold()) 对于背景分类,然后对前景进行分类,应用形态学,执行轮廓搜索,选择最大轮廓。 背景的样本图像可用于确定初始参数猜测的阈值。
边缘检测(Sobel / Scharr /等 - 定义最大边缘强度阈值,然后应用cv :: threshold())进行背景分类,然后对前景进行分类,应用形态学,执行轮廓搜索,选择最大轮廓。 背景的样本图像可用于确定初始参数猜测的阈值。
背景(褐色)或前景(灰色/白色)的颜色检测/分类,应用形态学,执行轮廓搜索,选择最大轮廓。 颜色检测应在HSV或LAB色彩空间中进行。 要采用稳健的方法,颜色检测应忽略亮度差异(否则文档的阴影可能无法识别为背景) 并且同时应该从颜色分类中排除太暗和太亮的像素。 背景的样本图像可用于确定典型的背景颜色,但由于背景中有浅灰色调,对象本身为灰色/白色 一个人应该手动提取颜色。
一般来说解决这个问题是比较困难的。 例如,如果使用颜色检测来查找灰色/白色文档,但将场景更改为灰色且不如文档亮的背景,则颜色 检测和分类将失败,因为我们将使用灰色来检测背景和前景。 在这种情况下,阈值方法将更好地工作。
因此,完全自动化的过程将很难实现。 我认为你必须使背景分类策略可以互换,这样你就可以轻松地改变它以使其适合场景。 当然,可以实现某种启发式方法,例如,可以分析来自背景的样本图像的噪声或标准偏差等。 如果存在大量噪声,则噪声检测/分类的方法是可行的。 如果噪声几乎为零,但与文档不同的原始亮度或颜色,则阈值或颜色检测/分类方法最适合。
以下是完全提取文档的第一种方法(简单阈值)的一些代码:
void drawRotatedRect(cv::Mat& drawing, cv::RotatedRect& rr, cv::Scalar color)
{
cv::Point2f points[4];
rr.points(points);
for (int j = 0; j < 4; j++)
{
cv::line(drawing, points[j], points[(j + 1) % 4], color, 1, 8);
}
}
// Adapted from http://answers.opencv.org/question/14807/fill-an-image-with-the-content-of-rotatedrect/
void drawRotatedRectFilled(cv::Mat& image, cv::RotatedRect rRect, cv::Scalar color)
{
cv::Point2f vertices2f[4];
cv::Point vertices[4];
rRect.points(vertices2f);
for (int i = 0; i < 4; ++i)
{
vertices[i] = vertices2f[i];
}
cv::fillConvexPoly(image, vertices, 4, color);
}
void testDocumentExtraction()
{
// Load image
std::string path = "./Testdata/Stackoverflow 1/";
std::string filename = "1.jpg";
std::string fqn = path + filename;
cv::Mat img = cv::imread(fqn, cv::IMREAD_COLOR);
auto imageSize = img.size();
// Convert to gray
cv::Mat imgGray;
cv::cvtColor(img, imgGray, CV_BGR2GRAY);
// Threshold with OTSU
cv::Mat imgBin;
int thresholdFlags = cv::ThresholdTypes::THRESH_BINARY + cv::ThresholdTypes::THRESH_OTSU;
cv::threshold(imgGray, imgBin, 0.0, 255.0, thresholdFlags);
// Morph
int erosionSize = 3;
int erosionType = cv::MORPH_RECT;
cv::Mat element = cv::getStructuringElement(erosionType,
cv::Size(2 * erosionSize + 1, 2 * erosionSize + 1),
cv::Point(erosionSize, erosionSize));
int nbMorphIterations = 2;
cv::Mat imgMorphed;
imgBin.copyTo(imgMorphed);
for (int i = 0; i < nbMorphIterations; ++i)
{
cv::erode(imgMorphed, imgMorphed, element);
}
for (int i = 0; i < 4; ++i)
{
cv::dilate(imgMorphed, imgMorphed, element);
cv::erode(imgMorphed, imgMorphed, element);
}
// Find contours.
// TODO: if we find more than one, use the largest one
std::vector<std::vector<cv::Point>> contours;
cv::Mat tempMat;
imgMorphed.copyTo(tempMat);
cv::findContours(tempMat, contours, cv::RETR_EXTERNAL, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE);
// Get contour of our (potential) object
auto contourOfObject = contours.at(0);
// Build object oriented bounding box (since the object is rectangular but potentially rotated).
auto oobb = cv::minAreaRect(contourOfObject);
// Draw contour or rotated rect filled to get a mask for the object.
// To have smoother roi borders, use rotated rect
cv::Mat maskForObject = cv::Mat::zeros(imageSize, CV_8U);
//cv::drawContours(maskForObject, contours, 0, cv::Scalar(255), cv::FILLED);
drawRotatedRectFilled(maskForObject, oobb, cv::Scalar(255));
// Draw the rotated rect in red color
cv::Mat drawing;
img.copyTo(drawing);
drawRotatedRect(drawing, oobb, cv::Scalar(0, 0, 255));
// Copy only the object from input image
cv::Mat imgWithOnlyTheObject = cv::Mat::zeros(imageSize, CV_8UC3);
img.copyTo(imgWithOnlyTheObject, maskForObject);
// Show all the stuff
cv::imshow("img", img);
cv::imshow("maskForObject", maskForObject);
cv::imshow("imgWithOnlyTheObject", imgWithOnlyTheObject);
cv::imshow("imgGray", imgGray);
cv::imshow("imgBin", imgBin);
cv::imshow("imgMorphed", imgMorphed);
cv::imshow("drawing", drawing);
cv::waitKey(0);
}
彩色图像 灰色图像 阈值图像 变形图像 面向对象的最大轮廓边界框 面向对象边界框的对象蒙版 从原始图像中提取的对象