形态学基本处理方法
基于图像形态进行处理的一些基本方法,这些处理方法基本是对二进制图像进行处理,卷积核决定着图像处理后的效果。
步骤:灰度图像->二值化->形态学运算
二值化
double threshold( InputArray src, OutputArray dst,
double thresh, double maxval, int type );
/* @param src 输入数组(多通道,8位或32位浮点)
@param dst 输出数组的大小和类型和通道数量与SRC相同.
@param thresh 阈值.
@param maxval 与#THRESH_BINARY和#THRESH_BINARY_INV阈值类型一起使用的最大值。
@param type 阈值类型 (see #ThresholdTypes).
@return 如果使用 Otsu's 或 Triangle方法,则为计算的阈值。
@sa 自适应阈值,寻找轮廓,比较,最小,最大*/
阈值类型
THRESH_BINARY = 0, //! 二值化(低于阈值变黑,高于阈值变为最大值)
THRESH_BINARY_INV = 1, //!反二值化(与之相反)
THRESH_TRUNC = 2, //! 大于阈值时变为阈值量,其他情况不变
THRESH_TOZERO = 3, //! 趋于零(仅地于阈值时变0,其他情况不变)
THRESH_TOZERO_INV = 4, //! 反趋于0(仅高于阈值时变0,其他情况不变)
THRESH_MASK = 7,
THRESH_OTSU = 8, //!< 使用Otsu algorithm 选择最优阈值
THRESH_TRIANGLE = 16 //!< 使用Triangle algorithm 选择最优阈值
全局二值化
cv::Mat thresholdImg;
// OTSU算法获取最优阈值
double optimal_threshold = cv::threshold(cartoonGray, thresholdImg, 0, 0, cv::THRESH_OTSU);
// 二值化处理
cv::threshold(cartoonGray, thresholdImg, optimal_threshold, 255, cv::THRESH_BINARY);
cv::imshow("cartoonGray", cartoonGray);
cv::imshow("thresholdImg", thresholdImg);
cv::waitKey(0);
局部二值化
void adaptiveThreshold( InputArray src, OutputArray dst,
double maxValue, int adaptiveMethod,
int thresholdType, int blockSize, double C );
@param src 源8位单通道图像。.
@param dst 目标图像的大小和类型与src相同.
@param maxValue 非零值赋给满足条件的像素
@param adaptiveMethod 要使用的自适应阈值算法,请参见#AdaptiveThresholdTypes。#BORDER_REPLICATE | #BORDER_ISOLATED 用于处理边界。
@param thresholdType 阈值类型,必须是其中之一 #THRESH_BINARY or #THRESH_BINARY_INV, see #ThresholdTypes.
@param blockSize 像素邻域的大小,用于计算像素的阈值:3、5、7等等。
@param C 常数减去平均值或加权平均值(详见下文)。通常,它是正的,但也可能是零或负的。
@sa threshold, blur, GaussianBlur
cv::Mat adaptiveThresholdImg;
// 自适应阈值二级化处理/局部二值化
cv::adaptiveThreshold(cartoonGray, adaptiveThresholdImg, 255,
cv::ADAPTIVE_THRESH_GAUSSIAN_C,
cv::THRESH_BINARY,3,0);
腐蚀和膨胀
腐蚀原理:锚点所在的像素点卷积核形状若没有全部覆盖白色区域,则变0(需要根据卷积核决定)。
膨胀原理:锚点所在的像素点卷积核形状若有一个接触到白色区域,则变1(需要根据卷积核决定)。
以下为腐蚀过程,传入一个5x5的全1卷积核,卷积核的锚点所重叠的像素点的颜色取决于,卷积核的形状覆盖全部白色区域时为1,反之则为0。最里面的虚线矩形为最终腐蚀后的图像。
cv::Mat erode_img, dilate_img;
// 获取卷积核(MORPH_CROSS十字形,RECT矩形,ELLIPSE椭圆形)
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(3, 3));
// 腐蚀
cv::erode(adaptiveThresholdImg, erode_img, kernel);
// 膨胀
cv::dilate(adaptiveThresholdImg, dilate_img, kernel);
cv::imshow("erode_img", erode_img);
cv::imshow("dilate_img", dilate_img);
图像形态学运算
void morphologyEx( InputArray src, OutputArray dst,
int op, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );
/* @param src 源图像。通道的数量可以是任意的。深度应为CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
@param dst 目标图像的大小和类型与源图像相同
@param op 形态操作的类型,请参见#MorphTypes
@param kernel 结构元素。可以使用#getStructuringElement.
@param anchor 与内核的锚定位置。负值表示锚点位于核中心
@param iterations 腐蚀和膨胀的次数。
@param borderType 像素外推方法,参见#BorderTypes。#BORDER_WRAP不支持。
@param borderValue 在边界不变的情况下的边界值。缺省值具有特殊含义。
@sa dilate, erode, getStructuringElement
@note 迭代次数是应用侵蚀或膨胀操作的次数。例如,具有两个迭代的打开操作(#MORPH_OPEN)等价于连续应用: erode -> erode -> dilate -> dilate (and not erode -> dilate -> erode -> dilate)。*/
开运算
先腐蚀再膨胀。作用是对背景去噪
闭运算
先膨胀再腐蚀。作用是对物件内部去噪
顶帽
原图 - 开运算。作用是提取背景的噪声
黑帽
原图 - 闭运算。作用是提取物件内部的噪声
代码
cv::Mat open_img, close_img, tophat_img, blackhat_img;
// 开运算
cv::morphologyEx(adaptiveThresholdImg, open_img, cv::MORPH_OPEN, kernel);
// 闭运算
cv::morphologyEx(adaptiveThresholdImg, close_img, cv::MORPH_CLOSE, kernel);
// 顶帽
cv::morphologyEx(adaptiveThresholdImg, tophat_img, cv::MORPH_TOPHAT, kernel);
// 黑帽
cv::morphologyEx(adaptiveThresholdImg, blackhat_img, cv::MORPH_BLACKHAT, kernel);
cv::imshow("srcImg", adaptiveThresholdImg);
cv::imshow("open_img", open_img);
cv::imshow("close_img", close_img);
cv::imshow("tophat_img", tophat_img);
cv::imshow("blackhat_img", blackhat_img);
cv::waitKey(0);
图像轮廓
轮廓:具有相同颜色和强度连续点的线条
作用:图形分析、物体的识别和检测
寻找轮廓
void findContours( InputArray image, OutputArrayOfArrays contours,
OutputArray hierarchy, int mode,
int method, Point offset = Point());
/*@param image 一个8位单通道图像。非零像素被视为1。零像素保持为0,因此图像被视为二值。你可以使用#compare,#inRange, #threshold ,#adaptive #Threshold, #Canny,等,以创建灰度或彩色的二值图像。如果mode等于#RETR_CCOMP或#RETR_FLOODFILL,则输入也可以是标签的32位整数图像(CV_32SC1)。
@param contours 检测到的轮廓。每个轮廓都存储为点的向量(e.g.
std::vector<std::vector<cv::Point> >).
@param hierarchy Optional output vector (e.g. std::vector<cv::Vec4i>), 包含映像拓扑信息。它的元素和等高线的数量一样多. 对于每i个轮廓 contours[i], 元素 hierarchy[i][0] , hierarchy[i][1] , hierarchy[i][2] , 和 hierarchy[i][3] 分别设置为同一层次级别下一个和上一个轮廓、第一个子轮廓和父轮廓的轮廓的基于0的指数。如果对于轮廓i没有下一个、上一个、父或嵌套的轮廓,则hierarchy[i] 对应的元素是负的。
@note 在Python中,层次结构嵌套在顶层数组中。使用hierarchy[0][i]访问第i个等高线的层次元素。
@param mode 轮廓检索模式,参见#RetrievalModes
@param method 轮廓逼近法,参见#ContourApproximationModes
@param offset 可选偏移量,每个等高线点通过该偏移量进行偏移。如果从图像ROI中提取轮廓,然后在整个图像上下文中进行分析,那么这是很有用的。 */
#RetrievalModes
/** 只检索极端的外部轮廓。它为所有轮廓设置' hierarchy[i][2]=hierarchy[i][3]=-1 '。 */
RETR_EXTERNAL = 0,
/** 检索所有轮廓,而不建立任何层次关系 */
RETR_LIST = 1,
/** 检索所有轮廓并将它们组织到一个两级层次结构中。在顶层,有组件的外部边界。在第二层,有洞的边界。如果连接组件的孔内有另一个轮廓线,它仍然放在顶层。*/
RETR_CCOMP = 2,
/** 检索所有轮廓并重构嵌套轮廓的完整层次结构。*/
RETR_TREE = 3,
RETR_FLOODFILL = 4 //!<
#ContourApproximationModes
/** 存储了所有的等高线点。也就是说,轮廓线的任意2个后续点(x1,y1)和(x2,y2)要么是水平的,要么是垂直的,要么是对角线,即max(abs(x1-x2),abs(y2-y1))==1。*/
CHAIN_APPROX_NONE = 1,
/** 压缩水平段、垂直段和对角线段,只留下它们的端点。例如,一个由上至右的矩形轮廓用4个点编码。 */
CHAIN_APPROX_SIMPLE = 2,
/** 应用Teh-Chin链近似算法的一种风格@cite TehChin89*/
CHAIN_APPROX_TC89_L1 = 3,
/** 应用Teh-Chin链近似算法的一种风格@cite TehChin89*/
CHAIN_APPROX_TC89_KCOS = 4
绘画轮廓
void drawContours( InputOutputArray image, InputArrayOfArrays contours,
int contourIdx, const Scalar& color,
int thickness = 1, int lineType = LINE_8,
InputArray hierarchy = noArray(),
int maxLevel = INT_MAX, Point offset = Point() );
/*@param image 目标图像.
@param contours 所有的输入轮廓。每个轮廓被存储为一个点向量。
@param contourIdx 指示要绘制的轮廓的参数。如果它是负的,就画出所有的等高线。
@param color 轮廓的颜色.
@param thickness 绘制等高线的线条粗细。如果为负值(例如,thickness=#FILLED),则绘制内部轮廓。
@param lineType 线连接度. 参见 #LineTypes
@param hierarchy 关于层次结构的可选信息。只有当你只想画一些等高线时才需要 (see maxLevel ).
@param maxLevel 绘制等高线的最大水平。如果为0,则只绘制指定的轮廓.
如果为1,则该函数绘制轮廓线和所有嵌套的轮廓线。如果是2,则表示函数
绘制等高线、所有嵌套等高线、所有嵌套到嵌套等高线,等等。 只有当存在可用的层次结构时,才会考虑此参数。
@param offset 轮廓移位参数。移动所有绘制的等高线的指定 \f$\texttt{offset}=(dx,dy)\f$ .
@note 当thickness=#FILLED时,该函数被设计为正确处理带有孔的连接组件,即使没有提供层次结构数据。 这是通过使用奇偶法则分析所有的轮廓来完成的。如果你有一个单独检索的轮廓的联合集合,这可能会给出不正确的结果。为了解决这个问题,您需要为每个等高线子组分别调用#drawContours,或者使用contourIdx参数遍历集合。*/
cv::Mat img_gray,thresholdImg;
// 转灰度图像
cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
// 二值化
cv::threshold(img_gray, thresholdImg, 180, 255, cv::THRESH_BINARY);
// 储存轮廓坐标的数组
std::vector<std::vector<cv::Point>> ContoursPointArray;
// 寻找轮廓
// RETR_CCOMP 存储顺序是最里面的为顶层,先到左边顶层由外到内编号,再到左边顶层由外到内编号;接着到第二层,先到左边第二层由外到内编号,再到左边第二层由外到内编号;由此类推。
// RETR_TREE 存储顺序是先右边由外向内,再往左边由外向内,依次编号(较常用)
// RETR_LIST 存储顺序是先找右边顶层的内框,再找左边顶层的内框,接着右外,再到左外;下一层亦是如此。
cv::findContours(thresholdImg, ContoursPointArray, cv::RETR_TREE, cv::CHAIN_APPROX_TC89_KCOS);
//std::cout << ContoursPointArray[0];
// 绘画轮廓
cv::drawContours(img, ContoursPointArray, -1, {
0,0,255 }, 1,16);
cv::imshow("img_gray", img_gray);
cv::imshow("src", img);
cv::imshow("thresholdImg", thresholdImg);
cv::waitKey(0);
RETR_CCOMP 存储顺序是最里面的为顶层,先到左边顶层由外到内编号,再到左边顶层由外到内编号;接着到第二层,先到左边第二层由外到内编号,再到左边第二层由外到内编号;由此类推。
RETR_TREE 存储顺序是先右边由外向内,再往左边由外向内,依次编号(较常用)
RETR_LIST 存储顺序是先找右边顶层的内框,再找左边顶层的内框,接着右外,再到左外;下一层亦是如此。
轮廓的面积和周长
/*@param contour 二维点(轮廓顶点)的输入向量,存储在std::vector或Mat中
@param oriented 定向区域标志。如果为true,函数将返回一个带符号的面积值,该值取决于轮廓方向(顺时针或逆时针)。使用这个特性,您可以通过取一个区域的符号来确定轮廓的方向。缺省情况下,该参数为false,即返回绝对值。*/
double contourArea( InputArray contour, bool oriented = false );
/*@param curve 二维点(轮廓顶点)的输入向量,存储在std::vector或Mat中
@param closed 指示曲线是否关闭的标志。*/
double arcLength( InputArray curve, bool closed );
多边形逼近和凸包
多边形逼近
void approxPolyDP( InputArray curve,
OutputArray approxCurve,
double epsilon, bool closed );
/*@param curve 二维点的输入向量存储在std::vector或Mat中
@param approxCurve 近似的结果。类型应该与输入曲线的类型相匹配.
@param epsilon 指定近似精度的参数。这是原始曲线与其近似值之间的最大距离。
@param closed 如果为真,则近似曲线是闭合的(它的第一个顶点和最后一个顶点是相连的)。否则,它没有关闭。*/
cv::Mat hand = cv::imread("C:\\Users\\10358\\Desktop\\img\\hand.jpg");
cv::Mat hand_gray, handThresholdImg;
// 转灰度图像
cv::cvtColor(hand, hand_gray, cv::COLOR_BGR2GRAY);
// 二值化 (阈值要恰当)
cv::threshold(hand_gray, handThresholdImg, 230, 255, cv::THRESH_BINARY_INV);
std::vector<std::vector<cv::Point>> handContoursPointArray;
std::vector<cv::Point> approxArray;
// 寻找轮廓
cv::findContours(handThresholdImg, handContoursPointArray,cv::RETR_TREE,cv::CHAIN_APPROX_SIMPLE);
// 多边形逼近
cv::approxPolyDP(handContoursPointArray[0], approxArray, 10, true);
// 绘画轮廓
cv::polylines(hand, approxArray, true, {
0,0,255 }, 3);
cv::imshow("handImg", hand);
cv::imshow("handThresholdImg", handThresholdImg);
cv::waitKey(0);
凸包
void convexHull( InputArray points, OutputArray hull,
bool clockwise = false, bool returnPoints = true );
/*@param points 输入2D点集,存储在std::vector或Mat中。
@param hull 输出凸包。它要么是指数的整数向量,要么是点的整数向量。在第一种情况下,包体元素是原始数组中凸包点的基于0的索引(因为凸包点集是原始点集的子集)。第二种情况,hull元素就是hull点本身。
@param clockwise 定位标志。如果为真,则输出凸包为顺时针方向。否则,它是逆时针方向的。假设的坐标系X轴指向右,Y轴指向上。
@param returnPoints 操作标记。对于矩阵,当标志为真时,函数返回凸包点。否则,它返回凸包点的索引。 当输出数组为 std::vector, 该标志被忽略。输出取决于向量的类型:std::vector<int>表示returnPoints=false, std::vector<Point>表示returnPoints=true。
@note ' points '和' hull '应该是不同的数组,不支持就地处理*/
cv::Mat hand = cv::imread("C:\\Users\\10358\\Desktop\\img\\hand.jpg");
cv::Mat hand_gray, handThresholdImg;
// 转灰度图像
cv::cvtColor(hand, hand_gray, cv::COLOR_BGR2GRAY);
// 二值化 (阈值要恰当)
cv::threshold(hand_gray, handThresholdImg, 230, 255, cv::THRESH_BINARY_INV);
std::vector<std::vector<cv::Point>> handContoursPointArray;
std::vector<cv::Point> approxArray, convexHullArray;
// 寻找轮廓
cv::findContours(handThresholdImg, handContoursPointArray,cv::RETR_TREE,cv::CHAIN_APPROX_SIMPLE);
// 多边形逼近
cv::approxPolyDP(handContoursPointArray[0], approxArray, 10, true);
// 绘画多边形逼近轮廓
cv::polylines(hand, approxArray, true, {
0,0,255 }, 3);
// 凸包
cv::convexHull(handContoursPointArray[0], convexHullArray);
// 绘画凸包轮廓
cv::polylines(hand, convexHullArray, true, {
0,0,255 }, 3);
cv::imshow("handImg", hand);
cv::imshow("handThresholdImg", handThresholdImg);
cv::waitKey(0);
外接矩形
最小外接矩形
cv::Mat img_gray,thresholdImg;
// 转灰度图像
cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
// 二值化
cv::threshold(img_gray, thresholdImg, 180, 255, cv::THRESH_BINARY);
std::vector<std::vector<cv::Point>> ContoursPointArray;
// 寻找轮廓
cv::findContours(thresholdImg, ContoursPointArray, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
// 最小外接矩阵
cv::RotatedRect min_rect = cv::minAreaRect(ContoursPointArray[0]);
//cv::boxPoints(min_rect, boxArray);
// 绘制圆心
cv::circle(img, cv::Point(min_rect.center.x, min_rect.center.y), 5, cv::Scalar(0, 255, 255), -1);
cv::Point2f rect[4];
// 存储点集
min_rect.points(rect);
for (int j = 0; j < 4; j++) {
line(img, rect[j], rect[(j + 1) % 4], cv::Scalar(0, 0, 255), 2, 8); //绘制最小外接矩形每条边
}
cv::imshow("src", img);
cv::imshow("thresholdImg", thresholdImg);
最大外接矩形
cv::Mat img_gray,thresholdImg;
// 转灰度图像
cv::cvtColor