ps 做ui比较好的网站,青岛做网站和小程序的公司,做网站的话术,网站seo方案操作系统#xff1a;ubuntu22.04 OpenCV版本#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言#xff1a;C11
描述
我们将学会使用基于标记的分水岭算法来进行图像分割。我们将看到#xff1a;watershed()函数的用法。 任何灰度图像都可以被视为一个地形表… 操作系统ubuntu22.04 OpenCV版本OpenCV4.9 IDE:Visual Studio Code 编程语言C11
描述
我们将学会使用基于标记的分水岭算法来进行图像分割。我们将看到watershed()函数的用法。 任何灰度图像都可以被视为一个地形表面其中高强度对应着山峰和丘陵而低强度则对应着山谷。你可以想象从每个孤立的山谷局部最小值开始用不同颜色的水标记来填充。随着水位上升依据附近的山峰梯度来自不同山谷的水显然带有不同的颜色将会开始融合。为了避免这种情况发生你必须在水开始汇合的地方建立起屏障。你持续进行填充水和构建屏障的工作直到所有的山峰都被水覆盖。此时你所建立的这些屏障就构成了分割的结果。这就是分水岭算法背后的理念。你可以在CMM网页上关于分水岭的页面通过观看一些动画来更直观地理解这个概念。 但是这种方法会因为图像中的噪声或其他不规则性而导致过度分割的结果。因此OpenCV实现了一种基于标记的分水岭算法其中你指明了哪些山谷点应该被合并哪些不应该。这是一种交互式的图像分割方式。我们所做的就是给已知的对象赋予不同的标记。将我们确信属于前景或对象的区域标记为一种颜色或强度将我们确信属于背景或非对象的区域标记为另一种颜色最后对于那些我们不确定的区域我们将其标记为0。这就是我们的标记。接着应用分水岭算法。随后我们的标记将被更新为我们给予的标签而对象的边界将拥有一个值为-1的特殊标记。
代码
假设我们有一张硬币的图像其中硬币彼此接触。即使你对图像进行了阈值处理硬币的边缘仍然会粘连在一起原图如下 我们开始着手于对硬币数量进行一个大致的估算。为此我们可以使用大津的二值化方法Otsu’s binarization。
#include opencv2/highgui.hpp
#include opencv2/imgcodecs.hpp
#include opencv2/imgproc.hpp
#include cstdio
#include iostream
#include opencv2/core/utility.hpp
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img imread( /media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg, 1 ), imgGray;if ( img.empty() ){cout Couldnt open image std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INVcv::THRESH_OTSU );cv::imshow( Original Image, img );cv::imshow( Gray Image, imgGray );cv::imshow( binary Image, binary );cv::waitKey( 0 );return 0;
}运行结果 现在我们需要去除图像中的任何细小的白色噪声。为此我们可以使用形态学开运算。为了消除物体上的任何微小孔洞我们可以使用形态学闭运算。因此我们现在可以确信靠近物体中心的区域是前景而远离物体的区域则是背景。唯一不确定的区域是硬币的边界区域。
所以我们需要提取那些我们确信是硬币的区域。腐蚀操作可以移除边界像素。因此剩下的区域我们可以确信那就是硬币。这在物体彼此不接触的情况下是可行的。但由于它们相互接触另一个好的选择是找到距离变换并应用一个适当的阈值。接下来我们需要找出那些我们确信不是硬币的区域。为此我们对结果进行膨胀处理。膨胀操作会使物体边界扩展到背景。这样一来我们就可以确保结果中处于背景中的任何区域确实是背景因为边界区域已经被去除了。请参见下图。
剩余的区域是我们无法确定是硬币还是背景的部分。这些不确定区域通常位于硬币边界处也就是前景与背景相遇的地方甚至可能是两个不同硬币相遇的区域。我们称这部分区域为边界区域。边界区域可以通过从确定的背景区域sure_bg中减去确定的前景区域sure_fg得到。 #include opencv2/highgui.hpp
#include opencv2/imgcodecs.hpp
#include opencv2/imgproc.hpp
#include cstdio
#include iostream
#include opencv2/core/utility.hpp
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img imread( /media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg, 1 ), imgGray;if ( img.empty() ){cout Couldnt open image std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INV cv::THRESH_OTSU );// noise removalcv::Mat kernel cv::Mat::ones( 3, 3, CV_8UC1 ) * 255;// 执行开运算cv::Mat opening;cv::morphologyEx( binary, opening, cv::MORPH_OPEN, kernel, cv::Point( -1, -1 ), 2 ); // 迭代次数为2cv::Mat sure_bg;// 执行膨胀操作cv::dilate(opening, sure_bg, kernel, cv::Point(-1,-1), 3); // 迭代次数为3cv::Mat dist_transform;// 执行距离变换cv::distanceTransform(opening, dist_transform, cv::DIST_L2, 3);cv::Mat sure_fg;double maxVal;// 查找矩阵中的最大值cv::minMaxLoc(dist_transform, nullptr, maxVal);// 设置阈值double thresholdValue 0.7 * maxVal;cv::threshold(dist_transform, sure_fg, thresholdValue, 255, cv::THRESH_BINARY);// Finding unknown regionsure_fg.convertTo(sure_fg, CV_8U);cv::Mat unknown;// 执行矩阵相减操作cv::subtract(sure_bg, sure_fg, unknown);// cv::imshow( 原始图, img );// cv::imshow( 灰度图, imgGray );// cv::imshow( 二值化后的图, binary );cv::imshow( sure_fg, sure_fg );cv::imshow( dist_transform, dist_transform );cv::waitKey( 0 );return 0;
}在阈值处理后的图像中如下图我们可以看到一些硬币区域我们确信这些区域属于硬币并且它们现在是分离的。在某些情况下你可能只对前景分割感兴趣而不关心相互接触的物体是否分离。在这种情况下你不需要使用距离变换仅仅使用腐蚀操作就足够了。腐蚀操作其实只是另一种提取确定前景区域的方法仅此而已。 现在我们已经确定了哪些区域属于硬币哪些属于背景。因此我们可以创建一个标记marker图像它与原始图像具有相同的尺寸但数据类型为int32。在这个标记图像中我们将确定的区域无论是前景还是背景标记为不同的正整数而不确定的区域则保持为零。
在OpenCV中我们可以使用cv::connectedComponentsWithStats函数来实现这一目的。该函数会将图像的背景标记为0其他对象则从1开始分配不同的整数标签。然而正如你所提到的如果背景被标记为0那么在Watershed算法中它将被视为未知区域。为了避免这种情况我们应该将未知区域即由unknown定义的区域标记为0而将背景标记为一个不同的整数。
#include opencv2/highgui.hpp
#include opencv2/imgcodecs.hpp
#include opencv2/imgproc.hpp
#include cstdio
#include iostream
#include opencv2/core/utility.hpp
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img imread( /media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg, 1 ), imgGray;if ( img.empty() ){cout Couldnt open image std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INV cv::THRESH_OTSU );// noise removalcv::Mat kernel cv::Mat::ones( 3, 3, CV_8UC1 ) * 255;// 执行开运算cv::Mat opening;cv::morphologyEx( binary, opening, cv::MORPH_OPEN, kernel, cv::Point( -1, -1 ), 2 ); // 迭代次数为2cv::Mat sure_bg;// 执行膨胀操作cv::dilate( opening, sure_bg, kernel, cv::Point( -1, -1 ), 3 ); // 迭代次数为3cv::Mat dist_transform;// 执行距离变换cv::distanceTransform( opening, dist_transform, cv::DIST_L2, 3 );cv::Mat sure_fg;double maxVal;// 查找矩阵中的最大值cv::minMaxLoc( dist_transform, nullptr, maxVal );// 设置阈值double thresholdValue 0.7 * maxVal;cv::threshold( dist_transform, sure_fg, thresholdValue, 255, cv::THRESH_BINARY );// Finding unknown regionsure_fg.convertTo( sure_fg, CV_8U );cv::Mat unknown;// 执行矩阵相减操作cv::subtract( sure_bg, sure_fg, unknown );// Marker labellingcv::Mat markers; // 将会存储标记结果// 执行连通组件标记int num_labels cv::connectedComponents( sure_fg, markers );cv::Mat ones cv::Mat::ones( markers.size(), markers.type() );// 将 markers 矩阵的所有元素值增加1cv::add( markers, ones, markers );// 创建一个与 markers 大小相同的掩码矩阵其中 unknown 矩阵中值为255的位置为 true其余位置为 falsecv::Mat mask unknown 255;// 将 markers 矩阵中对应于 mask 矩阵中 true 的位置的元素设置为0markers.setTo( 0, mask );// 创建一个与原图像大小相同的输出图像cv::Mat colorImage;// 将灰度图像转换为具有Jet色彩映射的彩色图像cv::applyColorMap(mask, colorImage, cv::COLORMAP_JET);// Add one to all labels so that sure background is not 0, but 1// cv::imshow( 原始图, img );// cv::imshow( 灰度图, imgGray );// cv::imshow( 二值化后的图, binary );cv::imshow( sure_fg, sure_fg );cv::imshow( dist_transform, dist_transform );cv::imshow( mask, colorImage );cv::waitKey( 0 );return 0;
}在应用了JET色彩映射的结果中红色区域代表了未知区域这是在硬币分割过程中尚未确定为硬币或背景的部分。确定的硬币区域则被赋予了不同的色彩值。而确定为背景的区域则以较浅的蓝色显示与未知区域的红色色形成对比。
现在我们的标记图像已经准备好了下一步就是应用Watershed算法。一旦应用了Watershed算法标记图像将会被修改。在硬币和背景之间的边界区域将会被标记为-1这是OpenCV中Watershed算法的一个特性它用-1来表示分割出的边界区域。
#include opencv2/highgui.hpp
#include opencv2/imgcodecs.hpp
#include opencv2/imgproc.hpp
#include cstdio
#include iostream
#include opencv2/core/utility.hpp
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img imread( /media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg, 1 ), imgGray;if ( img.empty() ){cout Couldnt open image std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INV cv::THRESH_OTSU );// noise removalcv::Mat kernel cv::Mat::ones( 3, 3, CV_8UC1 ) * 255;// 执行开运算cv::Mat opening;cv::morphologyEx( binary, opening, cv::MORPH_OPEN, kernel, cv::Point( -1, -1 ), 2 ); // 迭代次数为2cv::Mat sure_bg;// 执行膨胀操作cv::dilate( opening, sure_bg, kernel, cv::Point( -1, -1 ), 3 ); // 迭代次数为3cv::Mat dist_transform;// 执行距离变换cv::distanceTransform( opening, dist_transform, cv::DIST_L2, 3 );cv::Mat sure_fg;double maxVal;// 查找矩阵中的最大值cv::minMaxLoc( dist_transform, nullptr, maxVal );// 设置阈值double thresholdValue 0.7 * maxVal;cv::threshold( dist_transform, sure_fg, thresholdValue, 255, cv::THRESH_BINARY );// Finding unknown regionsure_fg.convertTo( sure_fg, CV_8U );cv::Mat unknown;// 执行矩阵相减操作cv::subtract( sure_bg, sure_fg, unknown );// Marker labellingcv::Mat markers; // 将会存储标记结果// 执行连通组件标记int num_labels cv::connectedComponents( sure_fg, markers );cv::Mat ones cv::Mat::ones( markers.size(), markers.type() );// 将 markers 矩阵的所有元素值增加1cv::add( markers, ones, markers );// 创建一个与 markers 大小相同的掩码矩阵其中 unknown 矩阵中值为255的位置为 true其余位置为 falsecv::Mat mask unknown 255;// 将 markers 矩阵中对应于 mask 矩阵中 true 的位置的元素设置为0markers.setTo( 0, mask );// 创建一个与原图像大小相同的输出图像cv::Mat colorImage;// 将灰度图像转换为具有Jet色彩映射的彩色图像cv::applyColorMap(mask, colorImage, cv::COLORMAP_JET);cv::imshow( 原始图, img );cv::watershed(img, markers);mask markers -1;img.setTo(cv::Scalar(255, 0, 0), mask);cv::imshow( watershed, img );cv::waitKey( 0 );return 0;
}