0


C++ opencv形态学、轮廓查找、特征检测和图像分割

C++ opencv形态学、轮廓查找、特征检测和图像分割

形态学基本处理方法

基于图像形态进行处理的一些基本方法,这些处理方法基本是对二进制图像进行处理,卷积核决定着图像处理后的效果。
步骤:灰度图像->二值化->形态学运算

二值化

doublethreshold( 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);

在这里插入图片描述

局部二值化

voidadaptiveThreshold( 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);

在这里插入图片描述

图像形态学运算

voidmorphologyEx( 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);

在这里插入图片描述
在这里插入图片描述

图像轮廓

轮廓:具有相同颜色和强度连续点的线条
作用:图形分析、物体的识别和检测

寻找轮廓

voidfindContours( 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

绘画轮廓

voiddrawContours( 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,即返回绝对值。*/doublecontourArea( InputArray contour,bool oriented =false);/*@param curve 二维点(轮廓顶点)的输入向量,存储在std::vector或Mat中
@param closed 指示曲线是否关闭的标志。*/doublearcLength( InputArray curve,bool closed );

多边形逼近和凸包

多边形逼近

voidapproxPolyDP( 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);

在这里插入图片描述

凸包

voidconvexHull( 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(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::Rect max_rect = cv::boundingRect(ContoursPointArray[0]);
    cv::rectangle(img, max_rect, cv::Scalar(0,255,255),1,16);
    
    cv::imshow("src", img);
    cv::imshow("thresholdImg", thresholdImg);

在这里插入图片描述

案例

车辆检测(简易)

// 检测线高度int lineHight =550;// 检测偏移量int offSet =4;// 车辆统计int count =0;int ESC =27;
    cv::VideoCapture video = cv::VideoCapture("C:\\Download\\video\\videovideo.mp4");
    cv::Mat frame, frameGray, frameThreshold, GausBlur,mask,erode_mask, dilate_mask, close_mask;// 创建背景
    cv::Ptr<cv::BackgroundSubtractorMOG2> bgsubmog2 = cv::createBackgroundSubtractorMOG2();// 卷积核
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Point(7,7));// 存储视频对象//cv::VideoWriter vw = cv::VideoWriter("C:\\Download\\video\\img.avi", cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 25, cv::Size(1280, 720));//cv::VideoWriter vw = cv::VideoWriter("C:\\Download\\video\\car.mp4", cv::VideoWriter::fourcc('m', 'p', '4', 'v'), 25, cv::Size(1280, 720));
    std::vector<cv::Point> cars ={};while(true){
        video.read(frame);if(frame.empty()){break;}// 灰度
        cv::cvtColor(frame, frameGray, cv::COLOR_RGB2GRAY);//double optimal_threshold = cv::threshold(frameGray, frameThreshold, 0, 0, cv::THRESH_OTSU);//cv::threshold(frameGray, frameThreshold, 80, 255, cv::THRESH_BINARY);// 去噪
        cv::GaussianBlur(frameGray, GausBlur, cv::Size(3,3),10);// 去背景
        bgsubmog2->apply(GausBlur, mask);// 腐蚀    
        cv::erode(mask, erode_mask, kernel);// 膨胀    
        cv::dilate(erode_mask, dilate_mask, kernel, cv::Point(-1,-1),3);// 闭运算 祛除物体内噪点
        cv::morphologyEx(dilate_mask, close_mask, cv::MORPH_CLOSE, kernel);
        cv::morphologyEx(close_mask, close_mask, cv::MORPH_CLOSE, kernel);// 寻找轮廓
        std::vector<std::vector<cv::Point>> carCts;
        cv::findContours(close_mask, carCts, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);// frame[1336 x 754]
        cv::line(frame, cv::Point(10, lineHight), cv::Point(1300, lineHight), cv::Scalar(255,255,0),3,16);for(size_t i =0; i < carCts.size(); i++){// 最大外接矩形
            cv::Rect max_rect = cv::boundingRect(carCts[i]);// 绘画目标矩形if(max_rect.width >110&& max_rect.height >110){
                cv::rectangle(frame, max_rect, cv::Scalar(0,0,255),1,16);// 将目标车辆的中心点加入数组
                cv::Point cPoint((int)(max_rect.x + max_rect.width /2),(int)(max_rect.y + max_rect.height /2));
                cars.push_back(cPoint);}// 检测通过车辆for(cv::Point point : cars){if(point.y < lineHight + offSet && point.y > lineHight - offSet){
                    count +=1;
                    cars.clear();}}}
        cv::putText(frame,"Cars Count: "+ std::to_string(count), cv::Point(600,60), cv::FONT_HERSHEY_SIMPLEX,2, cv::Scalar(255,255,0),3,16);// 写入帧/*cv::resize(frame, frame, cv::Size(1280, 720));
        vw.write(frame);*/
        cv::imshow("close_mask", close_mask);
        cv::imshow("frame", frame);if(cv::waitKey(1)== ESC){break;}}
    video.release();//vw.release();
    cv::destroyAllWindows();

特征检测

图像特征就是指有意义的图像区域具有独特性、易于识别性,比如角点、斑点以及高密度区。
角点:在特征中最重要的是角点,灰度梯度的最大值对应的像素,两条线的交点,极值点(一阶导数最大值,但二阶导数为0)

Harris角点检测:

光滑地区,无论向哪里移动,衡量系数不变。
边缘地址,垂直边缘移动时,衡量系统变化具烈。
在交点处,往那个方向移动,衡量系统都变化具烈。

voidcornerHarris( InputArray src, OutputArray dst,int blockSize,int ksize,double k,int borderType = BORDER_DEFAULT );/*
@param src 输入单通道8位或浮点图像。
@param dst 存储Harris检测器响应的图像。它的类型为CV_32FC1,大小与src相同。
@param blockSize 邻域大小,即扫描窗口的大小。
@param ksize Sobel算子的孔径参数。
@param k Harris探测器自由参数。参见上面的公式。
@param borderType 像素外推法。看到#BorderTypes。不支持#BORDER_WRAP。
*/
// 灰度图像
    cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);// harris角点检测
    cv::cornerHarris(img_gray, harris_img,2,3,0.04);// 获取mat最大值最小值及其坐标double minVal, maxVal;
    cv::Point minPoint, maxPoint;
    cv::minMaxLoc(harris_img,&minVal,&maxVal,&minPoint,&maxPoint);//std::cout << "minVal" << minVal << "maxVal" << maxVal;// 找出角点区域(BGR)for(size_t i =0; i < img.rows; i++){
        uchar* tmp = img.ptr<uchar>(i);
        float_t* tmp2 = harris_img.ptr<float_t>(i);for(size_t j =0, z=0; j <(size_t)img.cols*3; z++,j+=3){if((float_t)tmp2[z]> maxVal*0.01){//std::cout << "tmp2[z]" << (float_t)tmp2[z] << "maxVal" << maxVal;//tmp[j] = 255;//tmp[j+1]= 0;
                tmp[j+2]=255;}}}//找出角点区域(二值图)/*double threshold = 0.0001;
    cv::threshold(harris_img, harris_img,
        threshold, 255, cv::THRESH_BINARY_INV);*/
    cv::imshow("img", img);
    cv::imshow("img_gray", img_gray);
    cv::waitKey(0);

在这里插入图片描述

Shi-Tomasi角点检测

Shi-Tomasi是基于Harris角点检测所改进的。Harris 角点检测算的稳定性和k有关,而k是个经验值,不好设定最佳值。

voidgoodFeaturesToTrack( InputArray image, OutputArray corners,int maxCorners,double qualityLevel,double minDistance,
                                     InputArray mask =noArray(),int blockSize =3,bool useHarrisDetector =false,double k =0.04);/*
@param image 输入8位或浮点32位,单通道图像。
@param corners 检测角的输出向量。
@param maxCorners 返回的最大拐角数。如果找到的角比找到的多,就把其中最强的那个退回去。' maxCorners <= 0 '表示未设置最大值限制,并返回所有检测到的角。
@param qualityLevel 表征图像角的最小可接受质量的参数。参数值乘以最佳角质量度量值,即最小特征值(见#cornerMinEigenVal)或Harris函数响应(见#cornerHarris)。质量指标低于产品质量的拐角被拒收。例如,如果最佳角的质量度量值=1500,而qualityLevel=0.01,那么所有质量度量值小于15的角都将被拒绝。
@param minDistance 返回角之间的最小可能欧氏距离。
@param mask 可选的兴趣区域。如果图像不是空的(它需要有类型CV_8UC1并且与图像大小相同),它指定检测角的区域。
@param blockSize 在每个像素邻域上计算导数协变矩阵的平均块的大小。 See cornerEigenValsAndVecs
@param useHarrisDetector 是否使用Harris探测器的参数(see #cornerHarris)or #cornerMinEigenVal
@param k Harris探测器的自由参数。
*/
// 灰度图像
    cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);// Shi-Tomasi角点检测
    std::vector<cv::Point> Tomasi_array;
    cv::goodFeaturesToTrack(img_gray, Tomasi_array,1000,0.01,10);for(cv::Point point: Tomasi_array){
        cv::circle(img, point,2, cv::Scalar(0,0,255),-1);}
    cv::imshow("img", img);
    cv::imshow("img_gray", img_gray);
    cv::waitKey(0);

在这里插入图片描述

SIFT特征点检测

关键点:位置,大小和方向
关键点描述子:记录了关键点周围对其有贡献的像素点的一组向量值,其不受仿射变换、光照变换等影响
优点:关键点检测准确和描述子十分详细
缺点:检测速度慢

SIFT关键点

// SIFT特征点检测
    cv::Ptr<cv::SIFT> sift = cv::SIFT().create();// 创建SIFT
    std::vector<cv::KeyPoint> siftArray;// 获取关键点// detect(单通道图像,KeyPoint数组)
    sift->detect(img_gray, siftArray);// 绘画关键点// drawKeypoints(单通道图像,KeyPoint数组,输出图像)
    cv::drawKeypoints(img_gray, siftArray, img);
    
        
    cv::imshow("img", img);
    cv::imshow("img_gray", img_gray);
    cv::waitKey(0);

在这里插入图片描述

关键点描述子

// SIFT特征点检测//cv::Ptr<cv::SIFT> sift = cv::SIFT().create();        // 实例后创建SIFT
    cv::Ptr<cv::SIFT> sift = cv::SIFT::create();// 创建SIFT
    std::vector<cv::KeyPoint> siftArray;
    cv::Mat descriptorsArray;// 获取关键点和描述子// detectAndCompute(单通道图像,mask,KeyPoint数组, Mat数组)
    sift->detectAndCompute(img_gray, cv::Mat(),siftArray, descriptorsArray);// 第一个关键点的描述子
    std::cout <<descriptorsArray(cv::Range(0,1),cv::Range(0,descriptorsArray.cols));// 绘画关键点// drawKeypoints(单通道图像,KeyPoint数组,输出图像)
    cv::drawKeypoints(img_gray, siftArray, img);
    
    cv::imshow("img", img);
    cv::imshow("img_gray", img_gray);

在这里插入图片描述

SURF特征点检测

优点:检测速度得到了优化
缺点:准确性没SIFT高

SURF关键点和描述子

// SURF特征点检测
    cv::Ptr<cv::xfeatures2d::SURF> surf = cv::xfeatures2d::SURF::create();
    std::vector<cv::KeyPoint> surfArray;
    cv::Mat descriptorsArray;// 获取关键点和描述子// detectAndCompute(单通道图像,mask,KeyPoint数组, Mat数组)
    surf->detectAndCompute(img_gray, cv::Mat(), surfArray, descriptorsArray);// 绘画关键点// drawKeypoints(单通道图像,KeyPoint数组,输出图像)
    cv::drawKeypoints(img_gray, surfArray, img);
    
    cv::imshow("img", img);
    cv::imshow("img_gray", img_gray);
    cv::waitKey(0);

在这里插入图片描述

继承cv::xfeatures2d::SURF纯抽象类

抽象类: 成员函数中有纯虚函数,这种类叫抽象类,抽象类不能实例化 (不能创建对象)。 抽象类必须被继承且纯虚函数被覆盖后,由子类实例化对象。 如果继承抽象类,但没有覆盖纯虚函数,那么子类也将称为抽象类,不能实例化。
纯抽象类: 所有成员函数都是纯虚函数,这种只能被继承的类叫纯抽象类。 这种类一般用来设计接口,这种类在子类被替换后不需要被修改,或少量的修改即可继续使用。

classMySURF:public cv::xfeatures2d::SURF{public:MySURF(){
            hessianThreshold =0;
            nOctaves =0;
            nOctaveLayers =0;
            extended =0;
            upright =0;};protected:virtualvoidsetHessianThreshold(double hessianThreshold){this->hessianThreshold = hessianThreshold;};virtualdoublegetHessianThreshold()const{returnthis->hessianThreshold;};virtualvoidsetNOctaves(int nOctaves){this->nOctaves = nOctaves;};virtualintgetNOctaves()const{returnthis->nOctaves;};virtualvoidsetNOctaveLayers(int nOctaveLayers){this->nOctaveLayers = nOctaveLayers;};virtualintgetNOctaveLayers()const{returnthis->nOctaveLayers;};virtualvoidsetExtended(bool extended){this->extended = extended;};virtualboolgetExtended()const{returnthis->extended;};virtualvoidsetUpright(bool upright){this->upright = upright;};virtualboolgetUpright()const{return upright;};private:double hessianThreshold;int nOctaves, nOctaveLayers;bool extended, upright;};

继承类的使用

// SURF特征点检测
    cv::Ptr<cv::xfeatures2d::SURF> surf =MySURF().create();
    std::vector<cv::KeyPoint> surfArray;
    cv::Mat descriptorsArray;// 获取关键点和描述子// detectAndCompute(单通道图像,mask,KeyPoint数组, Mat数组)
    surf->detectAndCompute(img_gray, cv::Mat(), surfArray, descriptorsArray);// 绘画关键点// drawKeypoints(单通道图像,KeyPoint数组,输出图像)
    cv::drawKeypoints(img_gray, surfArray, img);
    
    cv::imshow("img", img);
    cv::imshow("img_gray", img_gray);
    cv::waitKey(0);

在这里插入图片描述

ORB实时特征检测

ORB = Oriented FAST + Rotated BRIEF
优点:可以做到实时检测
缺点:准确性不如SURF和SIFT

ORB的关键点和描述子

// ORB实时特征点检测
    cv::Ptr<cv::ORB> orb = cv::ORB::create();
    std::vector<cv::KeyPoint> ORBArray;
    cv::Mat descriptorsArray;// 获取关键点和描述子// detectAndCompute(单通道图像,mask,KeyPoint数组, Mat数组)
    orb->detectAndCompute(img_gray, cv::Mat(), ORBArray, descriptorsArray);// 绘画关键点// drawKeypoints(单通道图像,KeyPoint数组,输出图像)
    cv::drawKeypoints(img_gray, ORBArray, img);
    
    cv::imshow("img", img);
    cv::imshow("img_gray", img_gray);
    cv::waitKey(0);

在这里插入图片描述

特征点匹配

BF暴力匹配方法 原理:它使用第一组中的每个特征的描述子,与第二组中的所有特征描术子进行匹配计算它们之间的差距,然后将最接近一个匹配返回

voidmatch( InputArray queryDescriptors, InputArray trainDescriptors,
                CV_OUT std::vector<DMatch>& matches, InputArray mask=noArray())const;/*    @param queryDescriptors 描述子的查询集。
    @param trainDescriptors  描述子的训练集。 这个集合不会被添加到存储在类对象中的描述子的训练集合中.
    @param matches 匹配结果集. 如果描述子的查询集在mask中被屏蔽,则不为该描述子添加匹配。因此,匹配结果集大小可能小于描述子的查询集计数。
    @param mask 可以指定在输入的查询集和描述子的训练集之间允许的匹配
*/
voiddrawMatches( InputArray img1,const std::vector<KeyPoint>& keypoints1,
                             InputArray img2,const std::vector<KeyPoint>& keypoints2,const std::vector<DMatch>& matches1to2, InputOutputArray outImg,const Scalar& matchColor=Scalar::all(-1),const Scalar& singlePointColor=Scalar::all(-1),const std::vector<char>& matchesMask=std::vector<char>(), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT );/*
@param img1 第一张源图像.
@param keypoints1 第一个源图像的关键点.
@param img2 第二源图像.
@param keypoints2 第二个源图像的关键点.
@param matches1to2 从第一张图像匹配到第二张图像,这意味着keypoints1[i]在keypoints2[Matches [i]]中有一个对应的点。
@param outImg 输出图像。它的内容取决于定义在输出图像中绘制内容的flags值。请参阅下面可能的flags值。
@param matchColor 匹配的颜色(线和连接的关键点)。 如果matchColor==Scalar::all(-1), 颜色是随机生成的。
@param singlePointColor 单关键点的颜色 (圆圈), 这意味着关键点没有匹配. 如果singlePointColor==Scalar::all(-1) , 颜色是随机生成的。
@param matchesMask 掩码。决定绘制哪些匹配. 如果掩码为空,则绘制所有匹配项。
@param flags 标志设置绘图功能。可能的标志位值由DrawMatchesFlags定义。

这个函数从输出图像中的两个图像中提取关键点的匹配。匹配是连接两个关键点(圆)的一条线。参考cv::DrawMatchesFlags。
*/
    cv::Mat orig_gray, search_gray,result_img;// 灰度化
    cv::cvtColor(orig_img, orig_gray, cv::COLOR_BGR2GRAY);
    cv::cvtColor(search_img, search_gray, cv::COLOR_BGR2GRAY);// 创建SIFT
    cv::Ptr<cv::SIFT> sift = cv::SIFT::create();
    std::vector<cv::KeyPoint> origImgSiftArray, searchImgSiftArray;
    cv::Mat origImgDescriptorsArray, searchImgDescriptorsArray;// 获取两张图的关键点和描述子
    sift->detectAndCompute(orig_gray, cv::Mat(), origImgSiftArray, origImgDescriptorsArray);
    sift->detectAndCompute(search_gray, cv::Mat(), searchImgSiftArray, searchImgDescriptorsArray);// BF特征点匹配
    cv::BFMatcher bf = cv::BFMatcher();
    std::vector<cv::DMatch> matchArray;// 获取每个描述子最佳的匹配向量
    bf.match(origImgDescriptorsArray, searchImgDescriptorsArray, matchArray);// 绘画匹配集
    cv::drawMatches(orig_img, origImgSiftArray, search_img, searchImgSiftArray, matchArray, result_img);

    cv::imshow("result_img", result_img);
    cv::waitKey(0);

在这里插入图片描述

FLANN 最快邻近区特征匹配方法

速度快,精确差

voidknnMatch( InputArray queryDescriptors, InputArray trainDescriptors,
                   CV_OUT std::vector<std::vector<DMatch>>& matches,int k,
                   InputArray mask=noArray(),bool compactResult=false)const;/** @brief 从查询集中为每个描述子查找k个最佳匹配。

    @param queryDescriptors 描述子的查询集.
    @param trainDescriptors 描述子的训练集. 这个集合不会被添加到存储在类对象中的描述子的训练集合中。
    @param mask Mask 可以指定在输入的查询集和描述子的训练集之间允许的匹配
    @param matches Matches. 对于同一个查询描述子,每个Matches[i]是k个或更少的匹配。
    @param k 每个查询描述子找到的最佳匹配的计数,如果查询描述符的可能匹配总数少于k,则更少。
    @param compactResult 当mask(或多个mask)不为空时使用的参数。如果compactResult为false,则匹配向量的大小与queryDescriptors行相同。如果compactResult为true,则匹配向量不包含完全屏蔽查询描述符的匹配。
    这些DescriptorMatcher::match方法的扩展变体为每个查询描述符找到几个最佳匹配。匹配按距离递增顺序返回。有关查询和训练描述符的详细信息,请参见DescriptorMatcher::match
     */
    cv::Mat orig_gray, search_gray,result_img;// 灰度化
    cv::cvtColor(orig_img, orig_gray, cv::COLOR_BGR2GRAY);
    cv::cvtColor(search_img, search_gray, cv::COLOR_BGR2GRAY);// 创建SIFT
    cv::Ptr<cv::SIFT> sift = cv::SIFT::create();
    std::vector<cv::KeyPoint> origImgSiftArray, searchImgSiftArray;
    cv::Mat origImgDescriptorsArray, searchImgDescriptorsArray;// 获取两张图的关键点和描述子
    sift->detectAndCompute(orig_gray, cv::Mat(), origImgSiftArray, origImgDescriptorsArray);
    sift->detectAndCompute(search_gray, cv::Mat(), searchImgSiftArray, searchImgDescriptorsArray);
    std::vector<std::vector<cv::DMatch>> matches;
    std::vector<cv::DMatch> goodmatch;
    cv::FlannBasedMatcher flann = cv::FlannBasedMatcher();// knnMatch 返回k个最匹配的向量
    flann.knnMatch(origImgDescriptorsArray, searchImgDescriptorsArray, matches,2);// 过滤,寻找较好的匹配向量for(size_t i =0; i < matches.size(); i++){if(matches[i][0].distance <0.7* matches[i][1].distance){
            goodmatch.push_back(matches[i][0]);}}// 绘画匹配集
    cv::drawMatches(orig_img, origImgSiftArray, search_img, searchImgSiftArray, goodmatch, result_img);
    cv::imshow("result_img", result_img);
    cv::waitKey(0);

在这里插入图片描述

图像查找

Mat findHomography( InputArray srcPoints, InputArray dstPoints,int method =0,double ransacReprojThreshold =3,
                                 OutputArray mask=noArray(),constint maxIters =2000,constdouble confidence =0.995);/* @brief 查找两个平面之间的透视变换。
@param srcPoints 原始平面中各点的坐标,类型为CV_32FC2 or vector<Point2f>的矩阵。
@param dstPoints 原始平面中各点在目标平面上的坐标, 类型为CV_32FC2 or vector<Point2f>的矩阵。
@param method 用于计算单应矩阵的方法。有以下几种方法:
-   **0** - 一种使用所有点的常规方法,即最小二乘法
-   @ref RANSAC - RANSAC-基于鲁棒的方法
-   @ref LMEDS - Least-中位数鲁棒法
-   @ref RHO - PROSAC-基于鲁棒的方法
@param ransacReprojThreshold 将点对视为内嵌函数时允许的最大重投影误差(仅在RANSAC和RHO方法中使用)。
@param mask 可选的输出掩码由鲁棒的方法设置( RANSAC or LMeDS ). 注意,输入掩码值将被忽略
@param maxIters RANSAC迭代的最大次数。
@param confidence 置信度,在0到1之间。
*/
CV_EXPORTS_W voidperspectiveTransform(InputArray src, OutputArray dst, InputArray m );/*
@brief 执行向量的透视矩阵变换    
函数cv::perspective tivetransform通过将src的每个元素作为2D或3D向量进行转换,即在搜索图中找到原图的坐标点。
@note 函数转换一个稀疏的2D或3D向量集合。如果你想使用透视转换来转换图像,请使用arpPerspective . 如果你有一个逆问题,也就是说,你想从几对对应的点中计算最可能的透视转换,你可以使用getPerspectiveTransform or findHomography.
@param src 输入双通道或三通道浮点数组;每一个元素是要转换的2D/3D向量。
@param dst 输出数组的大小和类型与src相同。
@param m 3x3或4x4浮点变换矩阵。
@sa  transform, warpPerspective, getPerspectiveTransform, findHomography
*/
voidwarpPerspective( InputArray src, OutputArray dst,
                                   InputArray M, Size dsize,int flags = INTER_LINEAR,int borderMode = BORDER_CONSTANT,const Scalar& borderValue =Scalar());/* 
@brief 将透视转换应用于图像。warpPerspective函数使用指定的矩阵转换源图像:
@param src 输入图像
@param dst 输出图像,大小为dsize,类型与src相同
@param M 变换矩阵
@param dsize 输出图像的大小。
@param flags 插值方法的组合(#INTER_LINEAR或#INTER_NEAREST)和可选标志#WARP_INVERSE_MAP,它将M设置为逆变换 
@param borderMode 像素外推法 (#BORDER_CONSTANT or #BORDER_REPLICATE).
@param borderValue 用于固定边框的情况;缺省情况下,它等于0。
@sa  warpAffine, resize, remap, getRectSubPix, perspectiveTransform
 */
    cv::Mat orig_gray, search_gray,result_img;// 灰度化
    cv::cvtColor(orig_img, orig_gray, cv::COLOR_BGR2GRAY);
    cv::cvtColor(search_img, search_gray, cv::COLOR_BGR2GRAY);// 创建SIFT
    cv::Ptr<cv::SIFT> sift = cv::SIFT::create();
    std::vector<cv::KeyPoint> origImgSiftArray, searchImgSiftArray;
    cv::Mat origImgDescriptorsArray, searchImgDescriptorsArray;// 获取两张图的关键点和描述子
    sift->detectAndCompute(orig_gray, cv::Mat(), origImgSiftArray, origImgDescriptorsArray);
    sift->detectAndCompute(search_gray, cv::Mat(), searchImgSiftArray, searchImgDescriptorsArray);
    std::vector<std::vector<cv::DMatch>> matches;
    std::vector<cv::DMatch> goodmatch;
    cv::FlannBasedMatcher flann = cv::FlannBasedMatcher();// knnMatch 返回k个最匹配的向量
    flann.knnMatch(origImgDescriptorsArray, searchImgDescriptorsArray, matches,2);// 过滤,寻找较好的匹配向量for(size_t i =0; i < matches.size(); i++){if(matches[i][0].distance <0.7* matches[i][1].distance){
            goodmatch.push_back(matches[i][0]);}}// 有足够的匹配向量,才需要寻找if(goodmatch.size()>=4){
        std::vector<cv::Point2f> srcPoints, searchPoints;// 获取匹配向量中原图和搜索图相关的坐标for(size_t i =0; i < matches.size(); i++){// 获取匹配向量中原图的坐标
            srcPoints.push_back(origImgSiftArray[matches[i][0].queryIdx].pt);// 获取匹配向量中搜索图的坐标
            searchPoints.push_back(searchImgSiftArray[matches[i][0].trainIdx].pt);}// 单应性矩阵// RANSAC 随机抽样算法
        cv::Mat homoMat = cv::findHomography(srcPoints, searchPoints, cv::RANSAC,5.0);//cv::Mat homoMat = cv::getPerspectiveTransform(srcPoints, dstPoints, cv::RANSAC);
        std::vector<cv::Point2f> origBorderP ={{0,0},{0,(float)(orig_img.rows -1)},{(float)(orig_img.cols -1),(float)(orig_img.rows -1)},{(float)(orig_img.cols -1),0}};
        std::vector<cv::Point2f> origOnSearchP_f;
        std::vector<cv::Point2i> origOnSearchP_i;// 透视变换 cv::perspectiveTransform(原图坐标点,原图在搜索图上的坐标点,单应性矩阵) 在搜索图中找到原图的坐标点
        cv::perspectiveTransform(origBorderP, origOnSearchP_f, homoMat);//cv::warpPerspective(orig_img, search_img, homoMat, search_img.size());/*类型转换 mat to vector
        cv::Mat mat;
        std::vector<cv::Point2f> origOnSearchP_f = cv::Mat_<cv::Point2f>(mat);
        */// 数据类型转换 std::vector<cv::Point2f> to std::vector<cv::Point2i>
        cv::Mat(origOnSearchP_f).convertTo(origOnSearchP_i, CV_32SC1);
        cv::polylines(search_img, origOnSearchP_i,true, cv::Scalar(0,0,255),2,16);}// 绘画匹配集
    cv::drawMatches(orig_img, origImgSiftArray, search_img, searchImgSiftArray, goodmatch, result_img);
    cv::imshow("result_img", result_img);
    cv::waitKey(0);

cv::perspectiveTransform 效果图
在这里插入图片描述
cv::warpPerspective 效果图
在这里插入图片描述
在这里插入图片描述

案例图像拼接

获取单应性矩阵

cv::Mat findHomo(cv::Mat img1, cv::Mat img2, double confineValue=0.8)

cv::Mat findHomo(cv::Mat img1, cv::Mat img2,double confineValue=0.8){// 灰度化
    cv::Mat img1_gray, img2_gray;
    cv::cvtColor(img1, img1_gray, cv::COLOR_BGR2GRAY);
    cv::cvtColor(img2, img2_gray, cv::COLOR_BGR2GRAY);// 特征点检测
    cv::Ptr<cv::SIFT> sift = cv::SIFT::create();// 获取关键点和描述子
    std::vector<cv::KeyPoint> kp1, kp2;
    cv::Mat dp1, dp2;
    sift->detectAndCompute(img1_gray, cv::Mat(), kp1, dp1);
    sift->detectAndCompute(img2_gray, cv::Mat(), kp2, dp2);// 特征匹配
    cv::BFMatcher bfM = cv::BFMatcher();// 获取描述子的k个匹配向量
    std::vector< std::vector<cv::DMatch>> matchesArray;
    bfM.knnMatch(dp1, dp2, matchesArray,2);// 过滤匹配向量
    std::vector<cv::DMatch> goodMatches;for(std::vector<cv::DMatch> m: matchesArray){if(m[0].distance < confineValue * m[1].distance){
            goodMatches.push_back(m[0]);}}// 确保有足够的匹配向量if(goodMatches.size()>=8){// 根据匹配向量获取相关坐标
        std::vector<cv::Point2f> cp1, cp2;for(cv::DMatch e: goodMatches){
            cp1.push_back(kp1[e.queryIdx].pt);
            cp2.push_back(kp2[e.trainIdx].pt);}return cv::findHomography(cp1, cp2, cv::RANSAC,5.0);}else{
        std::cout <<"There are not enough matching vectors \n";exit(-1);}}

图像拼接

cv::Mat stitchImg(cv::Mat img1, cv::Mat img2, cv::Mat homo)

/// <summary>/// 图像拼接/// </summary>/// <param name="img1">搜索集</param>/// <param name="img2">训练集</param>/// <param name="homo1">img1 on img2单应性矩阵</param>/// <returns></returns>
cv::Mat stitchImg(cv::Mat img1, cv::Mat img2, cv::Mat homo){
    std::vector<cv::Point2f> img1Dims ={{0,0},{0,(float)img1.rows},{(float)img1.cols,(float)img1.rows},{(float)img1.cols,0}};
    std::vector<cv::Point2f> img2Dims ={{0,0},{0,(float)img2.rows},{(float)img2.cols,(float)img2.rows},{(float)img2.cols,0}};
    std::vector<cv::Point2f> resultImgDims;
    cv::perspectiveTransform(img2Dims, resultImgDims, homo);// 拼接数组//resultImgDims.insert(resultImgDims.begin(), { {0,0},{0,(float)img1.rows},{(float)img1.cols,(float)img1.rows},{(float)img1.cols,0} });//for (cv::Point2f point : img1Dims) resultImgDims.push_back(point);
    nc::NdArray<cv::Point2f> result = resultImgDims;
    nc::NdArray<cv::Point2f> result1 = img1Dims;
    nc::NdArray<cv::Point2f> concatenateP = nc::concatenate({ result,result1 });// 获取最大,最小的x和y
    nc::NdArray<float> tempArray ={ concatenateP[0].x, concatenateP[0].y };for(cv::Point2f point : concatenateP) tempArray = nc::append(tempArray,{ point.x, point.y }).reshape(-1,2);int maxP[]={(int)(tempArray.max(nc::Axis::ROW)[0]+0.5),(int)(tempArray.max(nc::Axis::ROW)[1]+0.5)};int minP[]={(int)(tempArray.min(nc::Axis::ROW)[0]-0.5),(int)(tempArray.min(nc::Axis::ROW)[1]-0.5)};
    std::cout << tempArray;
    std::cout << maxP[0]<< maxP[1]<<"\n";
    std::cout << minP[0]<< minP[1]<<"\n";// 平移透视之后的图片,定义平移变换矩阵
    cv::Mat transformArray = cv::Mat::eye(3,3,CV_64FC1);
    transformArray.at<double>(0,2)= std::abs((double)minP[0]);
    transformArray.at<double>(1,2)= std::abs((double)minP[1]);
    cv::Mat resultImg;//转换图像,将搜索集放于训练集适当位置
    cv::warpPerspective(img1, resultImg,  transformArray*homo, cv::Size(maxP[0]+ std::abs(minP[0]), maxP[1]+ std::abs(minP[1])));
    img2.copyTo(resultImg({ std::abs(minP[1]), std::abs(minP[1])+ img2.rows },{ std::abs(minP[0]), std::abs(minP[0])+ img2.cols }));return resultImg;}

main函数

intmain(){
    cv::Mat img1 = cv::imread(".\\img\\mountain1.jpg");
    cv::Mat img2 = cv::imread(".\\img\\mountain2.jpg");// 两张图像大小需一致
    cv::resize(img1, img1, cv::Size(640,480));
    cv::resize(img2, img2, cv::Size(640,480));// 获取img1 on img2单应性矩阵
    cv::Mat homo1 =findHomo(img1, img2);// 获取拼接后的图像
    cv::Mat resultImg1 =stitchImg(img1, img2, homo1);//获取img2 on img1单应性矩阵//cv::Mat homo2 = findHomo(img2, img1);//cv::Mat resultImg2 = stitchImg(img2, img1, homo2);

    cv::imshow("resultImg1", resultImg1);
    cv::imshow("img1", img1);
    cv::imshow("img2", img2);
    cv::waitKey(0);return0;}

在这里插入图片描述

图像分割

传统的图像分割方法

分水岭法

voiddistanceTransform( InputArray src, OutputArray dst,
                        OutputArray labels,int distanceType,int maskSize,int labelType = DIST_LABEL_CCOMP );voiddistanceTransform( InputArray src, OutputArray dst,int distanceType,int maskSize,int dstType=CV_32F);/** @brief 计算源图像中每个像素到最近的零像素的距离。
函数cv::distanceTransform计算二值图像中每个像素到最近的零像素的近似或精确距离。对于零像素的图像,距离显然为零。
当maskSize == #DIST_MASK_PRECISE和distanceType == #DIST_L2时,该函数将运行@cite Felzenszwalb04中描述的算法。该算法利用TBB库实现了并行化。
@param src 8位,单通道(二进制)源图像。
@param dst Output 具有计算距离的图像。它是一个8位或32位浮点的单通道映像,大小与src相同。
@param labels 输出二维标签数组(离散Voronoi图)。它的类型为CV_32SC1,大小与src相同。
@param distanceType 距离类型,请参见#DistanceTypes
@param maskSize 距离转换掩码的大小,参见#DistanceTransformMasks。在#DIST_L1或#DIST_C距离类型的情况下,参数被迫为3,因为3×3掩码给出的结果与5×5或任何更大的掩码相同。
@param dstType 输出图像的类型。请参见#DistanceTransformLabelTypes.它可以是CV_8U或CV_32F。类型CV_8U只能用于函数的第一个变体,并且distanceType == #DIST_L1
*/
intconnectedComponents(InputArray image, OutputArray labels,int connectivity,int ltype,int ccltype);intconnectedComponents(InputArray image, OutputArray labels,int connectivity =8,int ltype = CV_32S);/** @brief 计算布尔图像的标记图像的连接组件

@param image 要标记的8位单通道图像
@param labels 目的地标记图像
@param connectivity 8或4分别为8路或4路连接
@param ltype 输出图像标签类型。目前支持CV_32S和CV_16U。
@param ccltype 连接组件的算法类型(参见#ConnectedComponentsAlgorithmsTypes).
*/
    cv::Mat pillThreshold,pillGray,pillOpen,pillBGM, pillDist, pillFG,pillUnknow,pillMask;// 灰度化
    cv::cvtColor(pill, pillGray, cv::COLOR_BGR2GRAY);// 二值化
    cv::threshold(pillGray, pillThreshold,0,255, cv::THRESH_BINARY + cv::THRESH_OTSU);// 获取卷积核(MORPH_CROSS十字形,RECT矩形,ELLIPSE椭圆形)
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3));// 开运算()
    cv::morphologyEx(pillThreshold, pillOpen, cv::MORPH_OPEN, kernel,cv::Point(-1,-1));// 获取背景
    cv::dilate(pillOpen, pillBGM, kernel);// 获取前景
    cv::distanceTransform(pillOpen, pillDist, cv::DIST_L2,5);double distMax;
    cv::minMaxLoc(pillDist,0,&distMax);
    cv::threshold(pillDist, pillFG,0.55* distMax,255, cv::THRESH_BINARY);// 更改type(只允许相同通道)
    pillFG.convertTo(pillFG, CV_8UC1,255);//std::cout << pillBGM.type() << "\n" << pillFG.type(); 获取未知区域
    cv::subtract(pillBGM, pillFG, pillUnknow);创建连通域
    cv::connectedComponents(pillFG, pillMask);//pillMask += 1;for(size_t i =0; i < pillUnknow.rows; i++){for(size_t j =0; j < pillUnknow.cols; j++){if(pillUnknow.at<uchar>(i,j)==255){
                pillMask.at<std::int32_t>(i, j)=0;}}}// 分水岭算法 轮廓标配-1
    cv::watershed(pill, pillMask);for(size_t i =0; i < pillMask.rows; i++){for(size_t j =0; j < pillMask.cols; j++){if(pillMask.at<std::int32_t>(i, j)==-1){
                pill.at<uchar>(i, j*3)=0;
                pill.at<uchar>(i, j*3+1)=0;
                pill.at<uchar>(i, j*3+2)=255;}}}
    cv::imshow("pill", pill);
    cv::imshow("pillOpen", pillOpen);
    cv::imshow("pillDist", pillDist);
    cv::imshow("pillBGM", pillBGM);
    cv::imshow("pillFG", pillFG);
    cv::imshow("pillUnknow", pillUnknow);    
    cv::waitKey(0);

在这里插入图片描述

GrabCut法

static class GrabCutTest {
private:
    cv::Mat imgout, bgMask, fgMask,tem1,tem2;
    nc::NdArray<UINT8> ncMask;
public:
    static int startX;
    static int startY;
    static cv::Mat img;
    static bool flag_rectangle;
    static cv::Rect rect;
    static cv::Mat mask;GrabCutTest(){
        imgout = imgout.zeros(img.size(), CV_8UC3);
        //mask = mask.zeros(img.size(), CV_8UC1);
        tem1.create(img.size(), CV_8UC1);
        
        ncMask = nc::zeros<UINT8>(nc::Shape(img.rows, img.cols));};
    ~GrabCutTest(){
        NULL;};
    // 静态函数 属于类的方法,不属于对象,可直接调用
    static void mouseCallback(int event, int x, int y, int flags, void* userdata =0){
        std::cout <<"onmouse\n";if(event == cv::EVENT_LBUTTONDOWN){
            //std::cout <<"down\n";
            startX = x;
            startY = y;
            flag_rectangle =true;}elseif(event == cv::EVENT_LBUTTONUP){
            //std::cout <<"up\n";
            flag_rectangle =false;
            cv::rectangle(img, cv::Point(startX, startY), cv::Point(x, y), cv::Scalar(0, 0, 255), 2, 16);
            //mask.setTo(cv::Scalar::all(cv::GC_BGD));
            rect ={std::min(startX,x),std::min(startY,y), std::abs(x-startX),std::abs(y-startY)};
            //mask(rect).setTo(cv::Scalar(cv::GC_PR_FGD));}elseif(event == cv::EVENT_MOUSEMOVE){
            std::cout <<"move\n";if(flag_rectangle){
                cv::Mat img2;
                img.copyTo(img2);
                cv::rectangle(img2, cv::Point(startX, startY), cv::Point(x, y), cv::Scalar(0, 255, 255), 2, 16);
                cv::imshow("input", img2);
                cv::waitKey(100);}}
        //std::cout << event <<" "<< x <<" "<< y <<" "<< flags <<" "<< userdata << std::endl;};
    void run(){
        std::cout <<"run\n";
        cv::namedWindow("input");
        cv::setMouseCallback("input", mouseCallback);while(true){
            cv::imshow("input", img);
            cv::imshow("output", imgout);
            int key = cv::waitKey(100);if(key == int('q'))break;if(key == int('g')){
                // GC_FGD =1       // 属于前景色的像素
                // GC_BGD =0;       // 属于背景色的像素
                // GC_PR_FGD =3    // 可能属于前景的像素
                // GC_PR_BGD =2    // 可能属于背景的像素
                cv::grabCut(img, mask, rect, bgMask, fgMask, 1,cv::GC_INIT_WITH_RECT);}
            /*// 比较两个Mat矩阵是否相等
            if(memcmp(mask.data, tem1.setTo(cv::Scalar::all(cv::GC_FGD)).data, mask.total() * mask.elemSize())==0|| memcmp(mask.data, tem1.setTo(cv::Scalar::all(cv::GC_PR_FGD)).data, mask.total() * mask.elemSize())==0){}*/
            for(size_t i =0; i < mask.total() * mask.elemSize(); i++){
                uchar* temp = mask.data;if(!(*(temp + i)==1|| *(temp + i)==3)){
                    *(temp + i)=0;}}
            cv::bitwise_and(img, img, imgout, mask);
            //img.copyTo(imgout, mask);}};};
int GrabCutTest::startX =0;
int GrabCutTest::startY =0;
bool GrabCutTest::flag_rectangle =false;
cv::Mat GrabCutTest::img = cv::imread("./img/3.jpg");
cv::Rect GrabCutTest::rect = cv::Rect();
cv::Mat GrabCutTest::mask = mask.zeros(img.size(), CV_8UC1);

MeanShift法

void pyrMeanShiftFiltering( InputArray src, OutputArray dst,
                                         double sp, double sr, int maxLevel =1,
                                         TermCriteria termcrit=TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS,5,1));
/** 
@brief 执行图像的meanshift分割的初始步骤.
该函数实现了meanshift分割的滤波阶段,即函数的输出是经过滤波后的经过颜色渐变和细粒纹理压平的“分色”图像。
在输入图像(或缩小的输入图像,见下图)的每一个像素(X,Y)处,函数执行meanshift迭代,即考虑联合空间-颜色多维空间中的像素(X,Y)邻域:

\f[(x,y): X- \texttt{sp}\le x  \le X+ \texttt{sp} , Y- \texttt{sp}\le y  \le Y+ \texttt{sp} , ||(R,G,B)-(r,g,b)||\le \texttt{sr}\f]

其中(R,G,B)和(R,G,B)分别是颜色分量在(X,Y)和(X,Y)处的向量(不过,算法不依赖于所使用的颜色空间,所以可以使用任何3分量的颜色空间)。在邻域上找到平均空间值(X',Y')和平均颜色向量(R',G',B'),并在下一次迭代中充当邻域中心:

\f[(X,Y)~(X',Y'), (R,G,B)~(R',G',B').\f]

迭代结束后,初始像素(即迭代开始的像素)的颜色组件被设置为最终值(最后一次迭代的平均颜色):

\f[I(X,Y)<- (R*,G*,B*)\f]

当maxLevel>0时,构建maxLevel+1层的高斯金字塔,并先在最小的层上运行上述过程。在此之后,结果被传播到较大的层,迭代只在那些层颜色与金字塔的低分辨率层的差异超过sr的像素上再次运行。这使得颜色区域的边界更加清晰。注意,结果实际上与在原始图像上运行meanshift过程得到的结果不同(即当maxLevel==0时)。

@param src 8位3通道图像。
@param dst 与源图像具有相同格式和相同大小的目标图像。
@param sp 空间窗口半径。
@param sr 颜色窗口半径。
@param maxLevel 用于分割的金字塔的最大水平。
@param termcrit 终止标准:何时停止meanshift迭代
 */
voidCanny( InputArray image, OutputArray edges,double threshold1,double threshold2,int apertureSize =3,bool L2gradient =false/*
@param image 8位输入图象.
@param edges 输出边图;单通道8位图像,与图像大小相同。
@param threshold1 迟滞过程的第一个阈值。
@param threshold2 迟滞过程的第二个阈值。
@param apertureSize Sobel算子的孔径大小。
@param L2gradient 一个标志,表示是否有更精确的L_2范数
 */);
cv::Mat meanshiftImg,cannyImg;// pyrMeanShiftFiltering(输入图,输出图,空间窗口半径,颜色窗口半径):空间窗口半径和颜色窗口半径都需要调整// 空间窗口半径越大,轮廓越清晰,不过计算量大,耗时久;颜色窗口半径越大,物体的原有越模糊
    cv::pyrMeanShiftFiltering(img, meanshiftImg,100,30);
    cv::Canny(meanshiftImg, cannyImg,150,300);
    std::vector<std::vector<cv::Point>>  ContoursPointArray;
    cv::findContours(cannyImg, ContoursPointArray, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    cv::drawContours(img, ContoursPointArray,-1, cv::Scalar(0,0,255),2,16);

    cv::imshow("src", img);
    cv::imshow("meanshiftImg", meanshiftImg);

在这里插入图片描述

背景扣除法

cv::VideoCapture video = cv::VideoCapture("./video.mp4");
    cv::Ptr<cv::BackgroundSubtractorMOG2> mog = cv::createBackgroundSubtractorMOG2();/*createBackgroundSubtractorKNN(int history=500, double dist2Threshold=400.0,
                                   bool detectShadows=true);
    history是用于构建背景统计模型的帧数。该值越小,模型将越快考虑背景的变化,从而将其视为背景。反之亦然。
    dist2Threshold 是一个阈值,用于定义像素是否与背景不同。该值越小,运动检测越灵敏。反之亦然。
    detectShadows : 如果设置为 true,阴影将在生成的蒙版上以灰色显示*/
    cv::Ptr<cv::BackgroundSubtractorKNN> knn = cv::createBackgroundSubtractorKNN();while(true){
        cv::Mat frame,frameMask;
        video.read(frame);
        knn->apply(frame, frameMask);
        cv::imshow("video", frameMask);if(cv::waitKey(10)==(int)'q')break;}
    video.release();
    cv::destroyAllWindows();

人脸识别

// haar级联分类
    cv::CascadeClassifier HaarFaceCascade = cv::CascadeClassifier("./haarcascades/haarcascade_frontalface_alt.xml");// lbp级联分类 
    cv::CascadeClassifier lbpFaceCascade = cv::CascadeClassifier("D:/vsOpencv/opencv/sources/data/lbpcascades/lbpcascade_frontalface.xml");
    cv::Mat src = cv::imread("./img/face.jpeg");
    cv::Mat src2 = cv::imread("./img/face.jpeg");
    cv::Mat srcGray;
    cv::cvtColor(src, srcGray, cv::COLOR_RGB2GRAY);
    cv::equalizeHist(srcGray, srcGray);//直方图均值化,提升对比度,提升图像特征提取的准确率
    std::vector<cv::Rect> faces,faces2;// haar
    HaarFaceCascade.detectMultiScale(srcGray, faces);// lbp
    lbpFaceCascade.detectMultiScale(srcGray, faces2);for(size_t i =0; i < faces.size(); i++){
        cv::rectangle(src, faces[i], cv::Scalar(0,0,255),1,16);}for(size_t i =0; i < faces2.size(); i++){
        cv::rectangle(src2, faces2[i], cv::Scalar(0,0,255),1,16);}
    cv::putText(src,"face Count: "+ std::to_string(faces.size()), cv::Point(30,30), cv::FONT_HERSHEY_SIMPLEX,1, cv::Scalar(0,0,0),2,16);
    cv::putText(src2,"face Count: "+ std::to_string(faces2.size()), cv::Point(30,30), cv::FONT_HERSHEY_SIMPLEX,1, cv::Scalar(0,0,0),2,16);
    cv::imshow("haar", src);
    cv::imshow("lbp", src2);
    cv::waitKey(0);

在这里插入图片描述

Mat 矩阵

数据转换

Vector 和 Mat 互换

// Mat to Vector
std::vector<uchar> arr_u =(std::vector<uchar>)img.reshape(1,1);// reshape(新的通道数,新的row数)//vector to mat
cv::Mat array2Img(nc::NdArray<uchar> array,int n){
    size_t h = array.numRows();
    size_t w = array.numCols();
    cv::Mat img(h,(size_t)(w / n),CV_8UC(n));//保存为RGBfor(size_t i =0; i < h; i++){
        uchar* tmp = img.ptr<uchar>(i);for(size_t j =0; j < w; j++){
            tmp[j]=array(i, j);}}return img;}

数据类型转换

// Mat to Vector
std::vector<uchar> arr_u =(std::vector<uchar>)img.reshape(1,1);// reshape(新的通道数,新的row数)// vector数据类型转换 1
cv::Mat(arr_u).convertTo(arr_i, CV_32SC1);// vector数据类型转换 2// std::vector<int> arr_i = std::vector<int>(arr_u.begin(), arr_u.end());// vector数据类型转换 3//for (auto ele : arr_u)    arr_i.push_back(static_cast<int>(ele));

nc::NdArray<uchar> nArr_uc = arr_u;// NdArray数据类型转换
nc::NdArray<nc::int16> nArr_i16 = nArr_uc.astype<nc::int16>();

坐标点类型转换

// 数据类型转换 std::vector<cv::Point2f> to std::vector<cv::Point2i>
std::vector<cv::Point2f> origOnSearchP_f;
std::vector<cv::Point2i> origOnSearchP_i;
cv::Mat(origOnSearchP_f).convertTo(origOnSearchP_i, CV_32SC1);// 类型转换 mat to vector
cv::Mat mat;
std::vector<cv::Point2f> origOnSearchP_f = cv::Mat_<cv::Point2f>(mat);

本文转载自: https://blog.csdn.net/Vone_66/article/details/127741348
版权归原作者 Vone_66 所有, 如有侵权,请联系我们删除。

“C++ opencv形态学、轮廓查找、特征检测和图像分割”的评论:

还没有评论