目标
对于裂缝,在通过YOLO确定其大致位置的基础上,描出大概位置,计算出一系列的裂缝参数,如长度,面积,周长等。
处理思路
读取框选中的图像;
转为单通道灰度图;
自适应二值化;
此时发现图像(坝壁)会有局部噪声,这不是我们想要的,对图像开运算;
目前初步得到了二值化的裂缝图像,但还是远远不够的。
处理一:对二值图Canny后找轮廓,用最小矩形框选,判断该矩形中心是否在轮廓内。若是,则纳入“种子点”;
处理二:对二值图进行连通域搜索,判断连通域的中心是否为白色(即裂缝)。若是,则纳入“种子点”;
根据得到的种子点,在灰度图上进行漫灌,差值选定为常规值30;
漫灌后的图进行常规的二值化运算,阈值需要根据现场效果来调。此时得到的二值化图效果就比较好了,对白色点统计像素值就能得到面积,找轮廓就能得到周长。
对于长度计算,我采取的方法是尝试找出图像的中心线并标记出来。这里用到的是图像细化的实现,由于opencv的API在contrib包里,我就只能在网上找一种实现方法,下面是用到的两个函数,具体实现见代码。
cv::Mat thinImage(const cv::Mat& src, const int maxIterations)
void filterOver(cv::Mat thinSrc)
如此就能描出中线。
描出中线后,对中线点进行统计并计算各点之间的距离。(这个目前的方法不是很好)
目前我的处理方式是:对横向裂缝,先对列遍历、再对行遍历。对纵向裂缝则相反。如果两个点之间的横坐标或者纵坐标大于一个阈值,则不计算这两点之间的这段距离,避免比如 分叉、割断 带来的误差。【暂时没想到更好的方法】据此给出面积。

处理效果






存在问题
存在的问题在于裂缝长度的计算上。
比如横向的这种分叉的情况,应该怎么计算某一个分支的长度?
怎么将一个分支下的点相连呢?
实现代码
#pragma once#include<opencv2\opencv.hpp>#include<opencv2\dnn.hpp>#include<fstream>#include<iostream>#include "functions.h"//#include <opencv2\ximgproc.hpp> //细化函数thining所在的头文件using namespace std;using namespace cv;using namespace cv::dnn;#define path "D:/C_C++_Space/opencv4_xwl/cracktest3.jpg"#define pixdistance 1int main(){//---------------------------------------加载类别---------------------------------------bool crackflag = 1;if (!crackflag){return -1;}Mat src = imread(path);//后期改成在YOLO框选范围内搜索imshow("原图", src);Mat grayImg, binImg,openImg,curve;if (src.empty()){return -1;}//imshow("src", src);if (src.channels() == 3){cvtColor(src, grayImg, COLOR_BGR2GRAY);}//imshow("gray", grayImg);RNG rng(10086);////设置操作标志flagsint connectivity = 4; //连通邻域方式int maskVal = 255; //掩码图像的数值int flags = connectivity | (maskVal << 8) | FLOODFILL_FIXED_RANGE;//设置与选中像素点的差值Scalar loDiff = Scalar(30, 30, 30);Scalar upDiff = Scalar(30, 30, 30);////声明掩模矩阵变量Mat mask = Mat::zeros(src.rows + 2, src.cols + 2, CV_8UC1);int blockSize = 27;int constValue = 22;adaptiveThreshold(grayImg, binImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, blockSize, constValue);Mat element = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));morphologyEx(binImg, binImg, MORPH_OPEN, element);//imshow("binary", binImg);Canny(binImg, curve, 80, 200);vector<vector<Point>> contours;findContours(curve, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);/*drawContours(src, contours, -1, Scalar(0, 0, 255), 2);*/Point2f rect[4];vector<Rect> boundRect(contours.size()); //定义外接矩形集合vector<RotatedRect> box(contours.size()); //定义最小外接矩形集合vector<Point> floodseed;for (int i = 0; i < contours.size(); i++){box[i] = minAreaRect(Mat(contours[i])); //计算每个轮廓最小外接矩形box[i].points(rect); //把最小外接矩形四个端点复制给rect数组//for (int i = 0; i < 4; i++)// line(src, rect[i], rect[(i + 1) % 4], Scalar(0, 255, 0));/*circle(src, box[i].center, 2, Scalar(0, 255, 0), 2);*///double a1 = pointPolygonTest(contours[i], box[i].center, true);//true表示点到轮廓的距离double b1 = pointPolygonTest(contours[i], box[i].center, false);//false表示计算点与轮廓的位置关系-1表示外部,0在轮廓上,1在轮廓内Scalar newVal = Scalar(0,0,0);if (b1==1){floodseed.push_back(box[i].center);}}//imshow("src", src);//imshow("flood", grayImg);Mat out, stats, centroids;int number = connectedComponentsWithStats(binImg, out, stats, centroids, 8, CV_16U);Mat result = Mat::zeros(src.size(), src.type());int w = result.cols;int h = result.rows;for (int i = 1; i < number; i++){int center_x = centroids.at<double>(i, 0);int center_y = centroids.at<double>(i, 1);int binvalue = binImg.at<uchar>(center_y, center_x);if (binvalue==255){floodseed.push_back(Point(center_x, center_y));}}for (int i=0;i<floodseed.size();i++){floodFill(grayImg, mask, floodseed[i], Scalar(0,255,0), 0, loDiff, upDiff, flags);}//imshow("漫灌后", grayImg);//图像信息统计Mat finalImg;threshold(grayImg, finalImg, 85, 255, THRESH_BINARY_INV);//阈值这里取85////开运算//Mat newelement = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));//morphologyEx(finalImg, finalImg, MORPH_OPEN, newelement);medianBlur(finalImg, finalImg, 7);//查找白色像素点个数int counter = 0;Mat_<uchar>::iterator it = finalImg.begin<uchar>();Mat_<uchar>::iterator itend = finalImg.end<uchar>();for (; it != itend; ++it){if ((*it) > 0) counter += 1;//二值化后,像素点是0或者255}cout << "area = " << counter* pixdistance* pixdistance << endl;imshow("处理后的图", finalImg);Mat normalImg = finalImg / 255;//归一化,用于图像细化操作Mat bond = thinImage(normalImg);filterOver(bond);bond *= 255;//还原为细化后的灰度图imshow("细化后", bond);//查找白色像素点,但没想清楚如何计算裂缝的长度vector<Point> pwhite;if (bond.cols >= bond.rows)//横向裂缝{for (int j = 0; j < bond.cols; j++){for (int i = 0; i < bond.rows; i++){if (bond.at<uchar>(i, j) > 0)pwhite.push_back(Point(j, i));}}}else//纵向裂缝{for (int i = 0; i < bond.rows; i++){for (int j = 0; j < bond.cols; j++){if (bond.at<uchar>(i, j) > 0)pwhite.push_back(Point(j, i));}}}double length = 0;int xthreshold = src.cols * 0.1;int ythreshold = src.rows * 0.1;for (int i = 0; i < pwhite.size()-1; i++){int xdiff = abs(pwhite[i].x - pwhite[i + 1].x);int ydiff = abs(pwhite[i].y - pwhite[i + 1].y);if (xdiff > xthreshold || ydiff > ythreshold) continue;length += getDistance(pwhite[i], pwhite[i + 1]);src.at<Vec3b>(pwhite[i])[0] = 0;src.at<Vec3b>(pwhite[i])[1] = 0;src.at<Vec3b>(pwhite[i])[2] = 255;/*circle(src, pwhite[i], 2, Scalar(0, 0, 255), 1);*/}src.at<Vec3b>(pwhite[pwhite.size()-1])[0] = 0;src.at<Vec3b>(pwhite[pwhite.size()-1])[1] = 0;src.at<Vec3b>(pwhite[pwhite.size()-1])[2] = 255;cout << "length = "<<length * pixdistance << endl;imshow("src", src);waitKey(0);return 0;}
//图像细化的鼻祖代码cv::Mat thinImage(const cv::Mat& src, const int maxIterations){assert(src.type() == CV_8UC1);cv::Mat dst;int width = src.cols;int height = src.rows;src.copyTo(dst);int count = 0; //记录迭代次数while (true){count++;if (maxIterations != -1 && count > maxIterations) //限制次数并且迭代次数到达break;std::vector<uchar*> mFlag; //用于标记需要删除的点//对点标记for (int i = 0; i < height; ++i){uchar* p = dst.ptr<uchar>(i);for (int j = 0; j < width; ++j){//如果满足四个条件,进行标记// p9 p2 p3// p8 p1 p4// p7 p6 p5uchar p1 = p[j];if (p1 != 1) continue;uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);uchar p8 = (j == 0) ? 0 : *(p + j - 1);uchar p2 = (i == 0) ? 0 : *(p - dst.step + j);uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1);uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1);uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j);uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1);uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1);if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6){int ap = 0;if (p2 == 0 && p3 == 1) ++ap;if (p3 == 0 && p4 == 1) ++ap;if (p4 == 0 && p5 == 1) ++ap;if (p5 == 0 && p6 == 1) ++ap;if (p6 == 0 && p7 == 1) ++ap;if (p7 == 0 && p8 == 1) ++ap;if (p8 == 0 && p9 == 1) ++ap;if (p9 == 0 && p2 == 1) ++ap;if (ap == 1 && p2 * p4 * p6 == 0 && p4 * p6 * p8 == 0){//标记mFlag.push_back(p + j);}}}}//将标记的点删除for (std::vector<uchar*>::iterator i = mFlag.begin(); i != mFlag.end(); ++i){**i = 0;}//直到没有点满足,算法结束if (mFlag.empty()){break;}else{mFlag.clear();//将mFlag清空}//对点标记for (int i = 0; i < height; ++i){uchar* p = dst.ptr<uchar>(i);for (int j = 0; j < width; ++j){//如果满足四个条件,进行标记// p9 p2 p3// p8 p1 p4// p7 p6 p5uchar p1 = p[j];if (p1 != 1) continue;uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);uchar p8 = (j == 0) ? 0 : *(p + j - 1);uchar p2 = (i == 0) ? 0 : *(p - dst.step + j);uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1);uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1);uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j);uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1);uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1);if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6){int ap = 0;if (p2 == 0 && p3 == 1) ++ap;if (p3 == 0 && p4 == 1) ++ap;if (p4 == 0 && p5 == 1) ++ap;if (p5 == 0 && p6 == 1) ++ap;if (p6 == 0 && p7 == 1) ++ap;if (p7 == 0 && p8 == 1) ++ap;if (p8 == 0 && p9 == 1) ++ap;if (p9 == 0 && p2 == 1) ++ap;if (ap == 1 && p2 * p4 * p8 == 0 && p2 * p6 * p8 == 0){//标记mFlag.push_back(p + j);}}}}//将标记的点删除for (std::vector<uchar*>::iterator i = mFlag.begin(); i != mFlag.end(); ++i){**i = 0;}//直到没有点满足,算法结束if (mFlag.empty()){break;}else{mFlag.clear();//将mFlag清空}}return dst;}/*** @brief 对骨骼化图数据进行过滤,实现两个点之间至少隔一个空白像素* @param thinSrc为输入的骨骼化图像,8位灰度图像格式,元素中只有0与1,1代表有元素,0代表为空白*/void filterOver(cv::Mat thinSrc){assert(thinSrc.type() == CV_8UC1);int width = thinSrc.cols;int height = thinSrc.rows;for (int i = 0; i < height; ++i){uchar* p = thinSrc.ptr<uchar>(i);for (int j = 0; j < width; ++j){// 实现两个点之间至少隔一个像素// p9 p2 p3// p8 p1 p4// p7 p6 p5uchar p1 = p[j];if (p1 != 1) continue;uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);uchar p8 = (j == 0) ? 0 : *(p + j - 1);uchar p2 = (i == 0) ? 0 : *(p - thinSrc.step + j);uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - thinSrc.step + j + 1);uchar p9 = (i == 0 || j == 0) ? 0 : *(p - thinSrc.step + j - 1);uchar p6 = (i == height - 1) ? 0 : *(p + thinSrc.step + j);uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + thinSrc.step + j + 1);uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + thinSrc.step + j - 1);if (p2 + p3 + p8 + p9 >= 1){p[j] = 0;}}}}/*** @brief 从过滤后的骨骼化图像中寻找端点和交叉点* @param thinSrc为输入的过滤后骨骼化图像,8位灰度图像格式,元素中只有0与1,1代表有元素,0代表为空白* @param raudis卷积半径,以当前像素点位圆心,在圆范围内判断点是否为端点或交叉点* @param thresholdMax交叉点阈值,大于这个值为交叉点* @param thresholdMin端点阈值,小于这个值为端点* @return 为对src细化后的输出图像,格式与src格式相同,元素中只有0与1,1代表有元素,0代表为空白*/std::vector<cv::Point> getPoints(const cv::Mat& thinSrc, unsigned int raudis, unsigned int thresholdMax, unsigned int thresholdMin ){assert(thinSrc.type() == CV_8UC1);int width = thinSrc.cols;int height = thinSrc.rows;cv::Mat tmp;thinSrc.copyTo(tmp);std::vector<cv::Point> points;for (int i = 0; i < height; ++i){for (int j = 0; j < width; ++j){if (*(tmp.data + tmp.step * i + j) == 0){continue;}int count = 0;for (int k = i - raudis; k < i + raudis + 1; k++){for (int l = j - raudis; l < j + raudis + 1; l++){if (k < 0 || l < 0 || k>height - 1 || l>width - 1){continue;}else if (*(tmp.data + tmp.step * k + l) == 1){count++;}}}if (count > thresholdMax || count < thresholdMin){Point point(j, i);points.push_back(point);}}}return points;}/***************************************************************FunctionName: getDistancePurpose: 计算点之间的距离Parameter: 两个pointReturn: 两点之间的距离****************************************************************/double getDistance(Point pointO, Point pointA){double distance;distance = powf((pointO.x - pointA.x), 2) + powf((pointO.y - pointA.y), 2);distance = sqrtf(distance);return distance;}
Mat thinImage(const cv::Mat& src, const int maxIterations = -1);void filterOver(cv::Mat thinSrc);vector<cv::Point> getPoints(const cv::Mat& thinSrc, unsigned int raudis = 4, unsigned int thresholdMax = 6, unsigned int thresholdMin = 4);double getDistance(Point pointO, Point pointA);
