一、简介
1、分水岭算法
原理: 任何一副灰度图像都可以被看成拓扑平面,灰度值高的区域可以被看成是山峰,灰度值低的区域可以被看成是山谷。我们向每一个山谷中灌不同颜色的水。随着水的位的升高,不同山谷的水就会相遇汇合,为了防止不同山谷的水汇合,我们需要在水汇合的地方构建起堤坝。不停的灌水,不停的构建堤坝知直到所有的山峰都被水淹没,我们构建好的堤坝就是对图像的分割。这就是分水岭算法的背后原理。
在真实图像中,由于噪声点或者其它干扰因素的存在,使用分水岭算法常常存在过度分割的现象,这是因为很多很小的局部极值点的存在,如下图:
为了解决过度分割的问题,可以使用基于标记(mark)图像的分水岭算法,就是通过先验知识,来指导分水岭算法,以便获得更好的图像分段效果。通常的mark图像,都是在某个区域定义了一些灰度层级,在这个区域的洪水淹没过程中,水平面都是从定义的高度开始的,这样可以避免一些很小的噪声极值区域的分割。
2、分水岭算法函数 --- watershed()
1 CV_EXPORTS_W void watershed(InputArray image, InputOutArray markers);
image: 输入图像, 需为8位三通道彩色图像
markers: 参数调用后的结果, 输入/输出32位单通道图像标记结果, 需和原图一样的大小
1 //分水岭算法 2 #include "opencv2/opencv.hpp" 3 4 using namespace cv; 5 6 int main() 7 { 8 Mat srcImg = imread("bird.jpg"); 9 imshow("src", srcImg); 10 Mat dstImg = srcImg.clone(); 11 //medianBlur(srcImg, srcImg, 5); 12 //GaussianBlur(srcImg, srcImg, Size(5, 5), 0, 0); 13 Mat imgMask(srcImg.size(), CV_8U, Scalar(0));//标记图像 14 //标示背景图像 15 rectangle(imgMask, Point(1, 1), Point(srcImg.cols - 2, srcImg.rows - 2), Scalar(255), 4); 16 //标示鸟 17 rectangle(imgMask, Point(srcImg.cols / 2 - 10, srcImg.rows / 2 - 10), Point(srcImg.cols / 2 + 10, srcImg.rows / 2 + 10), Scalar(128), 10); 18 //标示岩石 19 rectangle(imgMask, Point(264, 353), Point(314, 395), Scalar(64), 10); 20 imshow("mask", imgMask); 21 22 imgMask.convertTo(imgMask, CV_32S); 23 watershed(srcImg, imgMask); //调用分水岭算法 24 Mat mark1, mark2; 25 imgMask.convertTo(mark1, CV_8U); 26 imshow("mark1", mark1); 27 28 Mat mark3 = mark1.clone(); 29 bitwise_not(mark1, mark2);//图像取反 30 imshow("mark2", mark2); 31 threshold(mark1, mark1, 64, 255, CV_THRESH_TOZERO_INV);//CV_THRESH_TOZERO_INV:大于阈值都为0,其余正常 找出岩石 32 threshold(mark2, mark2, 127, 255, CV_THRESH_TOZERO_INV);//因为取反后背景为0,所以筛选掉岩石后剩下的即为鸟 33 threshold(mark3, mark3, 128, 255, CV_THRESH_BINARY);//鸟为128,大于128才能为255,所以找出了背景 34 35 vector<vector<Point>> contours; 36 vector<Vec4i> hierarcy; 37 findContours(mark1, contours, hierarcy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); 38 drawContours(dstImg, contours, -1, Scalar(0, 255, 0), -1, 8); 39 40 vector<vector<Point>> contours2; 41 vector<Vec4i> hierarcy2; 42 findContours(mark2, contours2, hierarcy2, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); 43 drawContours(dstImg, contours2, -1, Scalar(0, 0, 255), -1, 8); 44 45 vector<vector<Point>> contours3; 46 vector<Vec4i> hierarcy3; 47 findContours(mark3, contours3, hierarcy3, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); 48 drawContours(dstImg, contours3, -1, Scalar(255, 0, 0), -1, 8); 49 50 Mat result = srcImg*0.5 + dstImg*0.5; 51 imshow("result", result); 52 waitKey(0); 53 return 0; 54 }
三、图像修补
OpenCV中图像修补技术由inpaint函数实现, 基本步骤是先修复区域边缘再逐步向内推进修复, 可以用来清除照片灰尘、划痕或者从静态图像及视频中去除不需要的物体。
图像修补---inpaint()
1 CV_EXPORTS_W void inpaint(InputArray src, InputArray inpaintMask, OutputArray dst, double inpaintRadius, int flags);
src: 输入图像, 需为8位单通道或三通道色图像
inpaintMask: 修复掩膜, 为八位单通道图像, 其中非0像素表示需要修补的区域
dst: 函数调用后输出图像, 和原图一样的尺寸和类型
inpaintRadius: 需要修补的每个点的圆形邻域, 为修复算法的参考半径
flags: 修补方法的标识符, 有如下两种:
1 INPAINT_NS=CV_INPAINT_NS, //Navier-Stokes algorithm 2 INPAINT_TELEA=CV_INPAINT_TELEA//A. Telea algorithm
基于Navier-Stokes方程方法和Alexandru Telea(需要添加opencv_photo库和photo.hpp)
1 #include "opencv2/opencv.hpp"//图像修复 2 3 using namespace cv; 4 5 int main() 6 { 7 Mat srcImg = imread("snow.jpg"); 8 9 Mat mask(srcImg.size(), CV_8U, Scalar(0)); 10 rectangle(mask, Point(45, 270), Point(180, srcImg.rows), Scalar(255), -1, 8); 11 12 Point org = Point(185, 335); 13 putText(srcImg, "OpenCV", org, CV_FONT_HERSHEY_SIMPLEX, 2.2f, CV_RGB(255, 0, 0), 2); 14 imshow("src", srcImg); 15 16 //putText( mask, "OpenCV", org, CV_FONT_HERSHEY_SIMPLEX, 2.2f, CV_RGB(255,255,255),2); 17 rectangle(mask, Point(185, 270), Point(srcImg.cols, srcImg.rows), Scalar(255), -1, 8); 18 imshow("mask", mask); 19 20 Mat result; 21 inpaint(srcImg, mask, result, 3, CV_INPAINT_NS); //图像修复 22 23 imshow("result", result); 24 waitKey(0); 25 return 0; 26 }