目标

对于裂缝,在通过YOLO确定其大致位置的基础上,描出大概位置,计算出一系列的裂缝参数,如长度,面积,周长等。

处理思路

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

image.png

处理效果

image.png
image.png
image.png
image.png
image.png
image.png

存在问题

存在的问题在于裂缝长度的计算上。
比如横向的这种分叉的情况,应该怎么计算某一个分支的长度?
A07C46FD28B50E5E21BD50BB77D69102.png
怎么将一个分支下的点相连呢?

实现代码

  1. #pragma once
  2. #include<opencv2\opencv.hpp>
  3. #include<opencv2\dnn.hpp>
  4. #include<fstream>
  5. #include<iostream>
  6. #include "functions.h"
  7. //#include <opencv2\ximgproc.hpp> //细化函数thining所在的头文件
  8. using namespace std;
  9. using namespace cv;
  10. using namespace cv::dnn;
  11. #define path "D:/C_C++_Space/opencv4_xwl/cracktest3.jpg"
  12. #define pixdistance 1
  13. int main()
  14. {
  15. //---------------------------------------加载类别---------------------------------------
  16. bool crackflag = 1;
  17. if (!crackflag)
  18. {
  19. return -1;
  20. }
  21. Mat src = imread(path);//后期改成在YOLO框选范围内搜索
  22. imshow("原图", src);
  23. Mat grayImg, binImg,openImg,curve;
  24. if (src.empty())
  25. {
  26. return -1;
  27. }
  28. //imshow("src", src);
  29. if (src.channels() == 3)
  30. {
  31. cvtColor(src, grayImg, COLOR_BGR2GRAY);
  32. }
  33. //imshow("gray", grayImg);
  34. RNG rng(10086);
  35. ////设置操作标志flags
  36. int connectivity = 4; //连通邻域方式
  37. int maskVal = 255; //掩码图像的数值
  38. int flags = connectivity | (maskVal << 8) | FLOODFILL_FIXED_RANGE;
  39. //设置与选中像素点的差值
  40. Scalar loDiff = Scalar(30, 30, 30);
  41. Scalar upDiff = Scalar(30, 30, 30);
  42. ////声明掩模矩阵变量
  43. Mat mask = Mat::zeros(src.rows + 2, src.cols + 2, CV_8UC1);
  44. int blockSize = 27;
  45. int constValue = 22;
  46. adaptiveThreshold(grayImg, binImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, blockSize, constValue);
  47. Mat element = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
  48. morphologyEx(binImg, binImg, MORPH_OPEN, element);
  49. //imshow("binary", binImg);
  50. Canny(binImg, curve, 80, 200);
  51. vector<vector<Point>> contours;
  52. findContours(curve, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
  53. /*drawContours(src, contours, -1, Scalar(0, 0, 255), 2);*/
  54. Point2f rect[4];
  55. vector<Rect> boundRect(contours.size()); //定义外接矩形集合
  56. vector<RotatedRect> box(contours.size()); //定义最小外接矩形集合
  57. vector<Point> floodseed;
  58. for (int i = 0; i < contours.size(); i++)
  59. {
  60. box[i] = minAreaRect(Mat(contours[i])); //计算每个轮廓最小外接矩形
  61. box[i].points(rect); //把最小外接矩形四个端点复制给rect数组
  62. //for (int i = 0; i < 4; i++)
  63. // line(src, rect[i], rect[(i + 1) % 4], Scalar(0, 255, 0));
  64. /*circle(src, box[i].center, 2, Scalar(0, 255, 0), 2);*/
  65. //double a1 = pointPolygonTest(contours[i], box[i].center, true);//true表示点到轮廓的距离
  66. double b1 = pointPolygonTest(contours[i], box[i].center, false);//false表示计算点与轮廓的位置关系-1表示外部,0在轮廓上,1在轮廓内
  67. Scalar newVal = Scalar(0,0,0);
  68. if (b1==1)
  69. {
  70. floodseed.push_back(box[i].center);
  71. }
  72. }
  73. //imshow("src", src);
  74. //imshow("flood", grayImg);
  75. Mat out, stats, centroids;
  76. int number = connectedComponentsWithStats(binImg, out, stats, centroids, 8, CV_16U);
  77. Mat result = Mat::zeros(src.size(), src.type());
  78. int w = result.cols;
  79. int h = result.rows;
  80. for (int i = 1; i < number; i++)
  81. {
  82. int center_x = centroids.at<double>(i, 0);
  83. int center_y = centroids.at<double>(i, 1);
  84. int binvalue = binImg.at<uchar>(center_y, center_x);
  85. if (binvalue==255)
  86. {
  87. floodseed.push_back(Point(center_x, center_y));
  88. }
  89. }
  90. for (int i=0;i<floodseed.size();i++)
  91. {
  92. floodFill(grayImg, mask, floodseed[i], Scalar(0,255,0), 0, loDiff, upDiff, flags);
  93. }
  94. //imshow("漫灌后", grayImg);
  95. //图像信息统计
  96. Mat finalImg;
  97. threshold(grayImg, finalImg, 85, 255, THRESH_BINARY_INV);//阈值这里取85
  98. ////开运算
  99. //Mat newelement = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
  100. //morphologyEx(finalImg, finalImg, MORPH_OPEN, newelement);
  101. medianBlur(finalImg, finalImg, 7);
  102. //查找白色像素点个数
  103. int counter = 0;
  104. Mat_<uchar>::iterator it = finalImg.begin<uchar>();
  105. Mat_<uchar>::iterator itend = finalImg.end<uchar>();
  106. for (; it != itend; ++it)
  107. {
  108. if ((*it) > 0) counter += 1;//二值化后,像素点是0或者255
  109. }
  110. cout << "area = " << counter* pixdistance* pixdistance << endl;
  111. imshow("处理后的图", finalImg);
  112. Mat normalImg = finalImg / 255;//归一化,用于图像细化操作
  113. Mat bond = thinImage(normalImg);
  114. filterOver(bond);
  115. bond *= 255;//还原为细化后的灰度图
  116. imshow("细化后", bond);
  117. //查找白色像素点,但没想清楚如何计算裂缝的长度
  118. vector<Point> pwhite;
  119. if (bond.cols >= bond.rows)//横向裂缝
  120. {
  121. for (int j = 0; j < bond.cols; j++)
  122. {
  123. for (int i = 0; i < bond.rows; i++)
  124. {
  125. if (bond.at<uchar>(i, j) > 0)
  126. pwhite.push_back(Point(j, i));
  127. }
  128. }
  129. }
  130. else//纵向裂缝
  131. {
  132. for (int i = 0; i < bond.rows; i++)
  133. {
  134. for (int j = 0; j < bond.cols; j++)
  135. {
  136. if (bond.at<uchar>(i, j) > 0)
  137. pwhite.push_back(Point(j, i));
  138. }
  139. }
  140. }
  141. double length = 0;
  142. int xthreshold = src.cols * 0.1;
  143. int ythreshold = src.rows * 0.1;
  144. for (int i = 0; i < pwhite.size()-1; i++)
  145. {
  146. int xdiff = abs(pwhite[i].x - pwhite[i + 1].x);
  147. int ydiff = abs(pwhite[i].y - pwhite[i + 1].y);
  148. if (xdiff > xthreshold || ydiff > ythreshold) continue;
  149. length += getDistance(pwhite[i], pwhite[i + 1]);
  150. src.at<Vec3b>(pwhite[i])[0] = 0;
  151. src.at<Vec3b>(pwhite[i])[1] = 0;
  152. src.at<Vec3b>(pwhite[i])[2] = 255;
  153. /*circle(src, pwhite[i], 2, Scalar(0, 0, 255), 1);*/
  154. }
  155. src.at<Vec3b>(pwhite[pwhite.size()-1])[0] = 0;
  156. src.at<Vec3b>(pwhite[pwhite.size()-1])[1] = 0;
  157. src.at<Vec3b>(pwhite[pwhite.size()-1])[2] = 255;
  158. cout << "length = "<<length * pixdistance << endl;
  159. imshow("src", src);
  160. waitKey(0);
  161. return 0;
  162. }
  1. //图像细化的鼻祖代码
  2. cv::Mat thinImage(const cv::Mat& src, const int maxIterations)
  3. {
  4. assert(src.type() == CV_8UC1);
  5. cv::Mat dst;
  6. int width = src.cols;
  7. int height = src.rows;
  8. src.copyTo(dst);
  9. int count = 0; //记录迭代次数
  10. while (true)
  11. {
  12. count++;
  13. if (maxIterations != -1 && count > maxIterations) //限制次数并且迭代次数到达
  14. break;
  15. std::vector<uchar*> mFlag; //用于标记需要删除的点
  16. //对点标记
  17. for (int i = 0; i < height; ++i)
  18. {
  19. uchar* p = dst.ptr<uchar>(i);
  20. for (int j = 0; j < width; ++j)
  21. {
  22. //如果满足四个条件,进行标记
  23. // p9 p2 p3
  24. // p8 p1 p4
  25. // p7 p6 p5
  26. uchar p1 = p[j];
  27. if (p1 != 1) continue;
  28. uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);
  29. uchar p8 = (j == 0) ? 0 : *(p + j - 1);
  30. uchar p2 = (i == 0) ? 0 : *(p - dst.step + j);
  31. uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1);
  32. uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1);
  33. uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j);
  34. uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1);
  35. uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1);
  36. if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6)
  37. {
  38. int ap = 0;
  39. if (p2 == 0 && p3 == 1) ++ap;
  40. if (p3 == 0 && p4 == 1) ++ap;
  41. if (p4 == 0 && p5 == 1) ++ap;
  42. if (p5 == 0 && p6 == 1) ++ap;
  43. if (p6 == 0 && p7 == 1) ++ap;
  44. if (p7 == 0 && p8 == 1) ++ap;
  45. if (p8 == 0 && p9 == 1) ++ap;
  46. if (p9 == 0 && p2 == 1) ++ap;
  47. if (ap == 1 && p2 * p4 * p6 == 0 && p4 * p6 * p8 == 0)
  48. {
  49. //标记
  50. mFlag.push_back(p + j);
  51. }
  52. }
  53. }
  54. }
  55. //将标记的点删除
  56. for (std::vector<uchar*>::iterator i = mFlag.begin(); i != mFlag.end(); ++i)
  57. {
  58. **i = 0;
  59. }
  60. //直到没有点满足,算法结束
  61. if (mFlag.empty())
  62. {
  63. break;
  64. }
  65. else
  66. {
  67. mFlag.clear();//将mFlag清空
  68. }
  69. //对点标记
  70. for (int i = 0; i < height; ++i)
  71. {
  72. uchar* p = dst.ptr<uchar>(i);
  73. for (int j = 0; j < width; ++j)
  74. {
  75. //如果满足四个条件,进行标记
  76. // p9 p2 p3
  77. // p8 p1 p4
  78. // p7 p6 p5
  79. uchar p1 = p[j];
  80. if (p1 != 1) continue;
  81. uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);
  82. uchar p8 = (j == 0) ? 0 : *(p + j - 1);
  83. uchar p2 = (i == 0) ? 0 : *(p - dst.step + j);
  84. uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1);
  85. uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1);
  86. uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j);
  87. uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1);
  88. uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1);
  89. if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6)
  90. {
  91. int ap = 0;
  92. if (p2 == 0 && p3 == 1) ++ap;
  93. if (p3 == 0 && p4 == 1) ++ap;
  94. if (p4 == 0 && p5 == 1) ++ap;
  95. if (p5 == 0 && p6 == 1) ++ap;
  96. if (p6 == 0 && p7 == 1) ++ap;
  97. if (p7 == 0 && p8 == 1) ++ap;
  98. if (p8 == 0 && p9 == 1) ++ap;
  99. if (p9 == 0 && p2 == 1) ++ap;
  100. if (ap == 1 && p2 * p4 * p8 == 0 && p2 * p6 * p8 == 0)
  101. {
  102. //标记
  103. mFlag.push_back(p + j);
  104. }
  105. }
  106. }
  107. }
  108. //将标记的点删除
  109. for (std::vector<uchar*>::iterator i = mFlag.begin(); i != mFlag.end(); ++i)
  110. {
  111. **i = 0;
  112. }
  113. //直到没有点满足,算法结束
  114. if (mFlag.empty())
  115. {
  116. break;
  117. }
  118. else
  119. {
  120. mFlag.clear();//将mFlag清空
  121. }
  122. }
  123. return dst;
  124. }
  125. /**
  126. * @brief 对骨骼化图数据进行过滤,实现两个点之间至少隔一个空白像素
  127. * @param thinSrc为输入的骨骼化图像,8位灰度图像格式,元素中只有0与1,1代表有元素,0代表为空白
  128. */
  129. void filterOver(cv::Mat thinSrc)
  130. {
  131. assert(thinSrc.type() == CV_8UC1);
  132. int width = thinSrc.cols;
  133. int height = thinSrc.rows;
  134. for (int i = 0; i < height; ++i)
  135. {
  136. uchar* p = thinSrc.ptr<uchar>(i);
  137. for (int j = 0; j < width; ++j)
  138. {
  139. // 实现两个点之间至少隔一个像素
  140. // p9 p2 p3
  141. // p8 p1 p4
  142. // p7 p6 p5
  143. uchar p1 = p[j];
  144. if (p1 != 1) continue;
  145. uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);
  146. uchar p8 = (j == 0) ? 0 : *(p + j - 1);
  147. uchar p2 = (i == 0) ? 0 : *(p - thinSrc.step + j);
  148. uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - thinSrc.step + j + 1);
  149. uchar p9 = (i == 0 || j == 0) ? 0 : *(p - thinSrc.step + j - 1);
  150. uchar p6 = (i == height - 1) ? 0 : *(p + thinSrc.step + j);
  151. uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + thinSrc.step + j + 1);
  152. uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + thinSrc.step + j - 1);
  153. if (p2 + p3 + p8 + p9 >= 1)
  154. {
  155. p[j] = 0;
  156. }
  157. }
  158. }
  159. }
  160. /**
  161. * @brief 从过滤后的骨骼化图像中寻找端点和交叉点
  162. * @param thinSrc为输入的过滤后骨骼化图像,8位灰度图像格式,元素中只有0与1,1代表有元素,0代表为空白
  163. * @param raudis卷积半径,以当前像素点位圆心,在圆范围内判断点是否为端点或交叉点
  164. * @param thresholdMax交叉点阈值,大于这个值为交叉点
  165. * @param thresholdMin端点阈值,小于这个值为端点
  166. * @return 为对src细化后的输出图像,格式与src格式相同,元素中只有0与1,1代表有元素,0代表为空白
  167. */
  168. std::vector<cv::Point> getPoints(const cv::Mat& thinSrc, unsigned int raudis, unsigned int thresholdMax, unsigned int thresholdMin )
  169. {
  170. assert(thinSrc.type() == CV_8UC1);
  171. int width = thinSrc.cols;
  172. int height = thinSrc.rows;
  173. cv::Mat tmp;
  174. thinSrc.copyTo(tmp);
  175. std::vector<cv::Point> points;
  176. for (int i = 0; i < height; ++i)
  177. {
  178. for (int j = 0; j < width; ++j)
  179. {
  180. if (*(tmp.data + tmp.step * i + j) == 0)
  181. {
  182. continue;
  183. }
  184. int count = 0;
  185. for (int k = i - raudis; k < i + raudis + 1; k++)
  186. {
  187. for (int l = j - raudis; l < j + raudis + 1; l++)
  188. {
  189. if (k < 0 || l < 0 || k>height - 1 || l>width - 1)
  190. {
  191. continue;
  192. }
  193. else if (*(tmp.data + tmp.step * k + l) == 1)
  194. {
  195. count++;
  196. }
  197. }
  198. }
  199. if (count > thresholdMax || count < thresholdMin)
  200. {
  201. Point point(j, i);
  202. points.push_back(point);
  203. }
  204. }
  205. }
  206. return points;
  207. }
  208. /***************************************************************
  209. FunctionName: getDistance
  210. Purpose: 计算点之间的距离
  211. Parameter: 两个point
  212. Return: 两点之间的距离
  213. ****************************************************************/
  214. double getDistance(Point pointO, Point pointA)
  215. {
  216. double distance;
  217. distance = powf((pointO.x - pointA.x), 2) + powf((pointO.y - pointA.y), 2);
  218. distance = sqrtf(distance);
  219. return distance;
  220. }
  1. Mat thinImage(const cv::Mat& src, const int maxIterations = -1);
  2. void filterOver(cv::Mat thinSrc);
  3. vector<cv::Point> getPoints(const cv::Mat& thinSrc, unsigned int raudis = 4, unsigned int thresholdMax = 6, unsigned int thresholdMin = 4);
  4. double getDistance(Point pointO, Point pointA);