OpenCV中为人熟知的提取连通域的函数是connectedComponents,但它是针对灰度图像的。其实,OpenCV中还有一个函数可以提取short或ushort图像的连通域且对其源码稍加修改就可以提取任何类型的矩阵的连通域,此函数就是filterSpeckles。它原本是用于后处理StereoBM和StereoSGBM生成的视差图,但我们完全可以对扩展其应用,用于提取任何类型矩阵的连通域。
filterSpeckles的关键函数有三个:newVal、maxSpeckleSize、maxDiff。
(1)maxDiff:相邻元素的值小于此值,认为是属于同一连通域
(2)maxSpeckleSize:连通域的像素个数小于此值,则被滤掉。
(3)newVal:被滤掉的连通域设置为此值。
OpenCV中寻找连通域使用的是深度搜索法DFS。这里,我提取其源码并增加了基于广度搜索法BFS的实现。通过对DFS和BFS,DFS效率更高,符合连通域提取原理。
以下是详细代码,依赖于C++14、OpenCV4.x和Spdlog。
1 #include <opencv2/opencv.hpp> 2 #include <spdlog/spdlog.h> 3 using namespace std; 4 using namespace cv; 5 #define RANDOM(min, max) (rand() % ((max) - (min) + 1) + (min)) 6 #define ns1970 chrono::time_point_cast<chrono::nanoseconds>(chrono::system_clock::now()).time_since_epoch().count() 7 class FilterSpeckles 8 { 9 public: 10 static void TestMe(int argc = 0, char** argv = 0) 11 { 12 int N = 999; 13 for (int k = 0; k < N; ++k) 14 { 15 //1.GenerateData 16 int rows = RANDOM(128, 1024); 17 int cols = RANDOM(128, 1024); 18 Mat_<uchar> src8u(rows, cols); randu(src8u, 0, UINT8_MAX); 19 Mat_<short> src16s(rows, cols); randu(src16s, 0, INT16_MAX); 20 int invalidV = -1; 21 int speckleSize = RANDOM(128, 1024); 22 int speckleRange = RANDOM(2, 128); 23 24 //2.CalcByOpenCV 25 Mat_<uchar> src8u0 = src8u.clone(); 26 Mat_<short> src16s0 = src16s.clone(); 27 int64 t00 = ns1970; cv::filterSpeckles(src8u0, invalidV, speckleSize, speckleRange); t00 = ns1970 - t00; 28 int64 t01 = ns1970; cv::filterSpeckles(src16s0, invalidV, speckleSize, speckleRange); t01 = ns1970 - t01; 29 30 //3.CalcByBFS 31 Mat_<uchar> src8u1 = src8u.clone(); 32 Mat_<short> src16s1 = src16s.clone(); 33 FilterSpeckles filter10(src8u1.rows, src8u1.cols, 3); 34 FilterSpeckles filter11(src16s1.rows, src16s1.cols, 3); 35 int64 t10 = ns1970; filter10.BFS(src8u1.ptr<uchar>(), invalidV, speckleSize, speckleRange); t10 = ns1970 - t10; 36 int64 t11 = ns1970; filter11.BFS(src16s1.ptr<short>(), invalidV, speckleSize, speckleRange); t11 = ns1970 - t11; 37 38 //4.CalcByDFS 39 Mat_<uchar> src8u2 = src8u.clone(); 40 Mat_<short> src16s2 = src16s.clone(); 41 FilterSpeckles filter20(src8u2.rows, src8u2.cols, 3); 42 FilterSpeckles filter21(src16s2.rows, src16s2.cols, 3); 43 int64 t20 = ns1970; filter20.DFS(src8u2.ptr<uchar>(), invalidV, speckleSize, speckleRange); t20 = ns1970 - t20; 44 int64 t21 = ns1970; filter21.DFS(src16s2.ptr<short>(), invalidV, speckleSize, speckleRange); t21 = ns1970 - t21; 45 46 //5.AnalyzeError 47 double infSrc8u0Src8u1 = norm(src8u0, src8u1); 48 double infSrc16s0Src16s1 = norm(src16s0, src16s1); 49 double infSrc8u0Src8u2 = norm(src8u0, src8u2); 50 double infSrc16s0Src16s2 = norm(src16s0, src16s2); 51 double sumFilters[4] = { 0, 0, 0, 0 }; 52 for (int i = 0; i < filter10.validAreas.size(); ++i) 53 for (int j = 0; j < filter10.validAreas[i].size(); ++j) 54 sumFilters[0] += std::abs(filter10.validAreas[i][j].x) + std::abs(filter10.validAreas[i][j].y) + std::abs(filter10.validAreas[i][j].z); 55 for (int i = 0; i < filter11.validAreas.size(); ++i) 56 for (int j = 0; j < filter11.validAreas[i].size(); ++j) 57 sumFilters[1] += std::abs(filter11.validAreas[i][j].x) + std::abs(filter11.validAreas[i][j].y) + std::abs(filter11.validAreas[i][j].z); 58 for (int i = 0; i < filter20.validAreas.size(); ++i) 59 for (int j = 0; j < filter20.validAreas[i].size(); ++j) 60 sumFilters[2] += std::abs(filter20.validAreas[i][j].x) + std::abs(filter20.validAreas[i][j].y) + std::abs(filter20.validAreas[i][j].z); 61 for (int i = 0; i < filter21.validAreas.size(); ++i) 62 for (int j = 0; j < filter21.validAreas[i].size(); ++j) 63 sumFilters[3] += std::abs(filter21.validAreas[i][j].x) + std::abs(filter21.validAreas[i][j].y) + std::abs(filter21.validAreas[i][j].z); 64 double infFilter10Filter20 = std::abs(sumFilters[0] - sumFilters[2]); 65 double infFilter11Filter21 = std::abs(sumFilters[1] - sumFilters[3]); 66 67 //6.PrintError 68 spdlog::info("LoopCount: {} timeVS: {} vs {} {} vs {}", k, t10 - t00, t20 - t00, t11 - t01, t21 - t01); 69 if (infSrc8u0Src8u1 > 0 || infSrc16s0Src16s1 > 0 || infSrc8u0Src8u2 > 0 || infSrc16s0Src16s2 > 0 || infFilter10Filter20 > 0 || infFilter11Filter21 > 0) 70 { 71 spdlog::info("5.1PrintError"); 72 spdlog::info("infSrc8u0Src8u1: {}", infSrc8u0Src8u1); 73 spdlog::info("infSrc16s0Src16s1: {}", infSrc16s0Src16s1); 74 spdlog::info("infSrc8u0Src8u2: {}", infSrc8u0Src8u2); 75 spdlog::info("infSrc16s0Src16s2: {}", infSrc16s0Src16s2); 76 spdlog::info("infFilter10Filter20: {}", infFilter10Filter20); 77 spdlog::info("infFilter11Filter21: {}", infFilter11Filter21); 78 spdlog::info("Press any key to continue"); 79 getchar(); 80 } 81 } 82 } 83 84 private://ImaSize 85 int rows = 0; 86 int cols = 0; 87 int total = 0; 88 int saveOption = 0; //0-none, 1-valid, 2-small, 3-both 89 90 private://BufData 91 int* labeldata = 0; 92 bool* typedata = 0; 93 Point3i* dfsdata = 0; 94 vector<char> buffer = {}; 95 96 public://AreaData 97 vector<Point3i> area = {};//Point2iForXY or intForPos 98 vector<vector<Point3i>> validAreas = {}; 99 vector<vector<Point3i>> smallAreas = {}; 100 101 public: 102 FilterSpeckles(int rows0, int cols0, int saveOption0) 103 { 104 //1. 105 rows = rows0; 106 cols = cols0; 107 total = rows * cols; 108 saveOption = saveOption0; 109 110 //2. 111 int bufSize = total * (sizeof(int) + sizeof(bool) + sizeof(Point3i)); 112 buffer.resize(bufSize); 113 labeldata = (int*)buffer.data(); 114 typedata = (bool*)(labeldata + total); 115 dfsdata = (Point3i*)(typedata + total); 116 117 //3. 118 area.reserve(total); 119 if (saveOption == 1 || saveOption == 3) validAreas.reserve(total); 120 if (saveOption == 2 || saveOption == 3) smallAreas.reserve(total); 121 } 122 template <typename dp> bool BFS(dp* imadata, int invalidV, int speckleSize, int speckleRange) 123 { 124 validAreas.clear(); smallAreas.clear(); 125 memset(labeldata, 0, total * sizeof(labeldata[0])); 126 for (int i = 0, label = 0; i < rows; ++i) 127 { 128 dp* imadataI = imadata + i * cols; 129 int* labeldataI = labeldata + i * cols; 130 131 for (int j = 0; j < cols; ++j) 132 { 133 //1.InvalidPoint 134 if (imadataI[j] == invalidV) continue; 135 136 //2.LabeledPoint 137 if (labeldataI[j]) 138 { 139 if (typedata[labeldataI[j]]) 140 imadataI[j] = (dp)invalidV; 141 continue; 142 } 143 144 //3.UnlabeledPoint 145 labeldataI[j] = ++label; 146 area.clear(); area.push_back(Point3i(j, i, i * cols + j)); 147 for (int k = 0, ii, jj, cur; k < area.size(); ++k) 148 { 149 ii = area[k].y; 150 jj = area[k].x; 151 cur = area[k].z; 152 dp* imadataX = imadata + cur; 153 int* labeldataX = labeldata + cur; 154 155 if (ii < rows - 1 && !labeldataX[+cols] && imadataX[+cols] != invalidV && abs(imadataX[0] - imadataX[+cols]) <= speckleRange) 156 { 157 labeldataX[+cols] = label; 158 area.push_back(Point3i(jj, ii + 1, (ii + 1) * cols + jj)); 159 } 160 161 if (ii > 0 && !labeldataX[-cols] && imadataX[-cols] != invalidV && abs(imadataX[0] - imadataX[-cols]) <= speckleRange) 162 { 163 labeldataX[-cols] = label; 164 area.push_back(Point3i(jj, ii - 1, (ii - 1) * cols + jj)); 165 } 166 167 if (jj < cols - 1 && !labeldataX[+1] && imadataX[+1] != invalidV && abs(imadataX[0] - imadataX[+1]) <= speckleRange) 168 { 169 labeldataX[+1] = label; 170 area.push_back(Point3i(jj + 1, ii, ii * cols + jj + 1)); 171 } 172 173 if (jj > 0 && !labeldataX[-1] && imadataX[-1] != invalidV && abs(imadataX[0] - imadataX[-1]) <= speckleRange) 174 { 175 labeldataX[-1] = label; 176 area.push_back(Point3i(jj - 1, ii, ii * cols + jj - 1)); 177 } 178 } 179 180 //4.MarkLabel 181 if (area.size() <= speckleSize) 182 { 183 typedata[labeldataI[j]] = true; 184 imadataI[j] = (dp)invalidV; 185 if (saveOption == 2 || saveOption == 3) smallAreas.push_back(area); 186 } 187 else 188 { 189 typedata[labeldataI[j]] = false; 190 if (saveOption == 1 || saveOption == 3) validAreas.push_back(area); 191 } 192 } 193 } 194 return true; 195 } 196 template <typename dp> bool DFS(dp* imadata, int invalidV, int speckleSize, int speckleRange) 197 { 198 validAreas.clear(); smallAreas.clear(); 199 memset(labeldata, 0, total * sizeof(labeldata[0])); 200 for (int i = 0, label = 0; i < rows; ++i) 201 { 202 dp* imadataI = imadata + i * cols; 203 int* labeldataI = labeldata + i * cols; 204 205 for (int j = 0; j < cols; ++j) 206 { 207 //1.InvalidPoint 208 if (imadataI[j] == invalidV) continue; 209 210 //2.LabeledPoint 211 if (labeldataI[j]) 212 { 213 if (typedata[labeldataI[j]]) 214 imadataI[j] = (dp)invalidV; 215 continue; 216 } 217 218 //3.UnlabeledPoint 219 labeldataI[j] = ++label; 220 dfsdata[0] = Point3i(j, i, i * cols + j); 221 area.clear(); area.push_back(dfsdata[0]); 222 for (int k = 0, ii, jj, cur; k >= 0; --k) 223 { 224 ii = dfsdata[k].y; 225 jj = dfsdata[k].x; 226 cur = dfsdata[k].z; 227 dp* imadataX = imadata + cur; 228 int* labeldataX = labeldata + cur; 229 230 if (ii < rows - 1 && !labeldataX[+cols] && imadataX[+cols] != invalidV && abs(imadataX[0] - imadataX[+cols]) <= speckleRange) 231 { 232 labeldataX[+cols] = label; 233 dfsdata[k] = Point3i(jj, ii + 1, (ii + 1) * cols + jj); 234 area.push_back(dfsdata[k++]); 235 } 236 237 if (ii > 0 && !labeldataX[-cols] && imadataX[-cols] != invalidV && abs(imadataX[0] - imadataX[-cols]) <= speckleRange) 238 { 239 labeldataX[-cols] = label; 240 dfsdata[k] = Point3i(jj, ii - 1, (ii - 1) * cols + jj); 241 area.push_back(dfsdata[k++]); 242 } 243 244 if (jj < cols - 1 && !labeldataX[+1] && imadataX[+1] != invalidV && abs(imadataX[0] - imadataX[+1]) <= speckleRange) 245 { 246 labeldataX[+1] = label; 247 dfsdata[k] = Point3i(jj + 1, ii, ii * cols + jj + 1); 248 area.push_back(dfsdata[k++]); 249 } 250 251 if (jj > 0 && !labeldataX[-1] && imadataX[-1] != invalidV && abs(imadataX[0] - imadataX[-1]) <= speckleRange) 252 { 253 labeldataX[-1] = label; 254 dfsdata[k] = Point3i(jj - 1, ii, ii * cols + jj - 1); 255 area.push_back(dfsdata[k++]); 256 } 257 } 258 259 //4.MarkLabel 260 if (area.size() <= speckleSize) 261 { 262 typedata[labeldataI[j]] = true; 263 imadataI[j] = (dp)invalidV; 264 if (saveOption == 2 || saveOption == 3) smallAreas.push_back(area); 265 } 266 else 267 { 268 typedata[labeldataI[j]] = false; 269 if (saveOption == 1 || saveOption == 3) validAreas.push_back(area); 270 } 271 } 272 } 273 return true; 274 } 275 }; 276 277 int main(int argc, char** argv) { FilterSpeckles::TestMe(argc, argv); return 0; }