本文用于实战OpenCV3 imgproc模块的基本方法!!!
具体函数定义可参考OpenCV3 Library API参考网站:
https://docs.opencv.org/3.0-beta/modules/refman.html
/* main.c */ #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <vector> #include <iostream> using namespace std; using namespace cv; #define WINDOW_NAME "window" int g_nThresholdValue = 100; int g_nThresholdType = 3; Mat g_thresholdImg, g_grayImg; static void on_Threshold(int, void*); int main(int *argc, int **argv) { Mat srcImg = imread("lena.jpg"); //1.线性滤波操作 Mat boxFilterImg, blurImg, GaussianBlurImg; //方框滤波 boxFilter(srcImg, boxFilterImg, -1, Size(5, 5)); imshow("boxFilter", boxFilterImg); //均值滤波 blur(srcImg, blurImg, Size(5, 5)); imshow("blur", blurImg); //高斯滤波 GaussianBlur(srcImg, GaussianBlurImg, Size(5, 5), 0, 0); imshow("GassianBlur", GaussianBlurImg); //2.非线性滤波 Mat medianBlurImg, bilateralFilterImg; //中值滤波 medianBlur(srcImg, medianBlurImg, 5); imshow("medianBlur", medianBlurImg); //双边滤波 bilateralFilter(srcImg, bilateralFilterImg, 5, 5 * 2, 5 / 2); imshow("bilateraFilter", bilateralFilterImg); //3.形态学滤波 Mat dilateImg, erodeImg, morphologyExImg; Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); //膨胀操作 dilate(srcImg, dilateImg, element); imshow("dilate", dilateImg); //腐蚀操作 erode(srcImg, erodeImg, element); imshow("erode", erodeImg); //高级形态学操作,开运算、闭运算、形态学梯度、顶帽、黑帽(MORPH_BLACKHAT) //通过设置不同的标识符选择不同的方法 morphologyEx(srcImg, morphologyExImg, MORPH_BLACKHAT, element); imshow("MORPH_BLACKHAT", morphologyExImg); //4.漫水填充 Rect rect; Mat floodFillImg = srcImg.clone(); floodFill(floodFillImg, Point(100, 100), Scalar(10, 10, 10), &rect, Scalar(10, 10, 10), Scalar(10, 10, 10)); imshow("floodFill", floodFillImg); //5.图像尺寸调整 Mat resizeImg1, resizeImg2; resize(srcImg, resizeImg1, Size(srcImg.cols / 2, srcImg.rows / 2), 0, 0, INTER_LINEAR); imshow("resize1", resizeImg1); resize(srcImg, resizeImg2, Size(), 0.5, 0.5, INTER_AREA); imshow("resize2", resizeImg2); //6.图像金字塔 Mat pyrUpImg, pyrDownImg; //向上采样 pyrUp(srcImg, pyrUpImg, Size(srcImg.cols * 2, srcImg.rows * 2)); imshow("pyrUp", pyrUpImg); //向下采样 pyrDown(srcImg, pyrDownImg, Size(srcImg.cols / 2, srcImg.rows / 2)); imshow("pyrDown", pyrDownImg); //7.固定阈值操作 cvtColor(srcImg, g_grayImg,COLOR_RGB2GRAY); namedWindow(WINDOW_NAME, WINDOW_AUTOSIZE);//先创建窗口再创建滑动条 createTrackbar("model", WINDOW_NAME, &g_nThresholdType, 4, on_Threshold); createTrackbar("threshold", WINDOW_NAME, &g_nThresholdValue, 255, on_Threshold); on_Threshold(0, 0); //8.Canny边缘检测 Mat grayImg, edgeImg, colorImg; colorImg.create(srcImg.size(), srcImg.type()); colorImg = Scalar::all(0); cvtColor(srcImg, grayImg, COLOR_RGB2GRAY); blur(grayImg, edgeImg, Size(3, 3)); Canny(edgeImg, edgeImg, 3, 9, 3); imshow("gray edge", edgeImg); srcImg.copyTo(colorImg, edgeImg); imshow("color edge", colorImg); //9.重映射,不改变像素值,仅移动像素位置 Mat map_x, map_y, remapImg; map_x.create(srcImg.size(), CV_32FC1); map_y.create(srcImg.size(), CV_32FC1); for (int j = 0; j < srcImg.rows; ++j) { for (int i = 0; i < srcImg.cols; ++i) { map_x.at<float>(j, i) = static_cast<float>(map_x.cols - i); map_y.at<float>(j, i) = static_cast<float>(map_y.rows - j); } } remap(srcImg, remapImg, map_x, map_y, INTER_LINEAR); imshow("remap", remapImg); //10.仿射变换 仿射变换旋转 Mat warpImg, warpRotImg; Point2f srcWarp[3],dstWarp[3]; srcWarp[0] = Point2f(0, 0); srcWarp[1] = Point2f(static_cast<float>(srcImg.cols - 1), 0); srcWarp[2] = Point2f(0, static_cast<float>(srcImg.rows - 1)); dstWarp[0] = Point2f(0.0, static_cast<float>(srcImg.rows *0.4)); dstWarp[1] = Point2f(static_cast<float>(srcImg.cols *0.8), static_cast<float>(srcImg.rows *0.3)); dstWarp[2] = Point2f(static_cast<float>(srcImg.cols *0.5), static_cast<float>(srcImg.rows *0.6)); Mat warpMat(2,3,CV_32FC1); //获得仿射矩阵 warpMat = getAffineTransform(srcWarp, dstWarp); warpAffine(srcImg, warpImg, warpMat, srcImg.size()); imshow("warpImg", warpImg); Mat warpRotMat(2, 3, CV_32FC1); Point2f center=Point(warpImg.cols/2, warpImg.rows/2); //获得旋转矩阵 warpRotMat = getRotationMatrix2D(center, 30, 0.5); warpAffine(warpImg, warpRotImg, warpRotMat, srcImg.size()); imshow("warpRotImg", warpRotImg); //11.直方图均衡化 Mat tempImg = srcImg.clone(); Mat equalizeImg; vector<Mat> channels; split(tempImg, channels); Mat blueImg = channels.at(0); equalizeHist(blueImg, equalizeImg);//处理蓝色通道 imshow("equalizeHist", equalizeImg); //12.查找并绘制轮廓 Mat grayImg, contoursImg; contoursImg = Mat::zeros(srcImg.size(), CV_8UC3); cvtColor(srcImg, grayImg, COLOR_RGB2GRAY); grayImg = grayImg > 50;//灰度图二值化 vector<vector<Point>> contours; vector<Vec4i> hierarchy; findContours(grayImg, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);//寻找轮廓 int index = 0; for (; index >= 0; index = hierarchy[index][0])//将下一个轮廓的索引编号赋值给index { Scalar color(255, 255, 255); drawContours(contoursImg, contours, index, color, CV_FILLED, 8, hierarchy); } imshow("grayIage", grayImg); imshow("contours", contoursImg); //13.寻找凸包,绘制凸包 Mat whiteImg(512,512,CV_8UC3); whiteImg = Scalar::all(0); RNG &rng = theRNG(); int countRNG = (unsigned)rng % 100 + 3; vector<Point> points; for (int i = 0; i < countRNG; ++i)//生成随机数 { Point point; point.x = rng.uniform(whiteImg.cols/4, whiteImg.cols/4 * 3); point.y = rng.uniform(whiteImg.rows / 4, whiteImg.rows / 4 * 3); points.push_back(point); } Scalar color = Scalar::all(255); for (int j = 0; j < countRNG; ++j)//绘制随机数 circle(whiteImg, points[j], 3, color, CV_FILLED, CV_AA); vector<int> hull; convexHull(Mat(points), hull, true);//返回凸包点在点集中的索引 imshow("points", whiteImg); int hullCount = (int)hull.size(); Point point0 = points[hull[hullCount - 1]]; for (int k = 0; k < hullCount; ++k)//连接各个凸包点 { Point point = points[hull[k]]; line(whiteImg, point0, point, Scalar::all(255), 2, 8); point0 = point; } imshow("hull", whiteImg); //14.RGB转化为HSV色彩空间 Mat hsvImg; cvtColor(srcImg, hsvImg, COLOR_RGB2HSV); imshow("hsvImg", hsvImg); while (1) { char c = waitKey(5);//获取按键 if ((char)c == 49)//按键为1,退出窗口 break; } return 0; } static void on_Threshold(int, void*) { threshold(g_grayImg, g_thresholdImg, g_nThresholdValue, 255, g_nThresholdType); imshow(WINDOW_NAME, g_thresholdImg); }
所用知识:
1.若以彩色模式载入图像,即通道顺序为RGB,解码后的图像以BGR的通道顺序进行存储;
2.waitKey()函数的功能是不断刷新图像,频率时间为delay,单位为ms,返回值为当前键盘按键值。delay>0时,延迟"delay"ms,在显示视频时这个函数是有用的,用于设置在显示完一帧图像后程序等待"delay"ms再显示下一帧视频;如果使用waitKey(0)则只会显示第一帧视频;
3. copyTo最一般的用法是src.copyTo(dst),将src复制到dst矩阵中;src.copyTo( dst,detected_edges); 是将src中detected_edges矩阵对应的非零部分(即边缘检测结果)复制到dst中。所以最终显示的边缘和原图颜色一样,也可以直接显示detected_edges矩阵(黑白)。
4.现实世界的三维空间映射到图像显示的二维空间中会丢失很多信息,也会添进来一部分类似光照、场景等的干扰。如何获得二维图像中的边缘?图像处理中认为,灰度值变化剧烈的地方就是边缘。那么如何判断灰度值变化?如何度量“剧烈”?sobel算子就对这些问题做了自己的规范,而且命名为sobel算子。
sobel算子的思想,Sobel算子认为,邻域的像素对当前像素产生的影响不是等价的,所以距离不同的像素具有不同的权值,对算子结果产生的影响也不同。一般来说,距离越远,产生的影响越小。
sobel算子的原理,对传进来的图像像素做卷积,卷积的实质是在求梯度值,或者说给了一个加权平均,其中权值就是所谓的卷积核;然后对生成的新像素灰度值做阈值运算,以此来确定边缘信息。
Sobel算子边缘检测过程:(1)滤波预处理,减少噪声对sobel算子的影响;(2) 对原图像素进行卷积获得新的像素(X方向上和Y方向上的梯度);(3) 给定阈值,大于阈值的为边缘,不大于的不为边缘;
5.膨胀、腐蚀、开、闭运算是数学形态学最基本的变换。
膨胀:求局部最大值(卷积),把二值图像各1像素连接成分的边界扩大一层(填充边缘或0像素内部的孔);
腐蚀:求局部最小值(卷积),把二值图像各1像素连接成分的边界点去掉从而缩小一层(可提取骨干信息,去掉毛刺,去掉孤立的0像素);开:先腐蚀再膨胀,可以去掉目标外的孤立点;闭:先膨胀再腐蚀,可以去掉目标内的孔。
6.漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色,这是个非常有用的功能,经常用来标记或者分离图像的一部分进行处理或分析.漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点. 漫水填充不会填充掩膜mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。另外需要注意的是,掩膜mask会比需填充的图像大,所以 mask 中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。
在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。
这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。
参考资料:
1.毛星云等编著《OpenCV3编程入门》;
2.CSDN相关博客和其他网页资料,因为是随用随查,仅将关键内容存于笔记中,然后在此贴出,所以并未保存链接;