本文以边缘检测为例,提供一个机器视觉原生算法的入门案例。效果如下图,左边是源图片,右边是检测结果:
基本思路:逐行扫描探测边缘;对每行的边缘位置做直线拟合;使用方差剔除缺损点。
边缘探测算法:使用类似【2, 2, 2, -2, -2, -2】的卷积算子,在边缘位置,卷积值最大,利用最大值两边的次大值作比例均衡,提高精度。
废话不多说了,上代码:
1 class CDetectVHLine 2 { 3 public: 4 CDetectVHLine(BYTE* image, int width, int height, int stride, 5 int left, int right, int bottom, int top, 6 int halfEdge, bool horizontal, bool blackToWhite) 7 { 8 m_image = image; 9 m_width = width; 10 m_height = height; 11 m_stride = stride; 12 13 m_left = left; 14 m_right = right; 15 m_bottom = bottom; 16 m_top = top; 17 18 m_halfEdge = halfEdge; 19 m_horizontal = horizontal; 20 m_blackToWhite = blackToWhite; 21 22 // 生成检测算子 23 m_factor.resize(m_halfEdge); 24 for (int i = 0; i < m_halfEdge; ++i){ 25 m_factor[i] = 2; 26 } 27 28 // 卷积区间要去除边缘 29 if (m_horizontal) { 30 m_bottom += m_halfEdge; 31 m_top -= m_halfEdge; 32 } 33 else { 34 m_left += m_halfEdge; 35 m_right -= m_halfEdge; 36 } 37 } 38 ~CDetectVHLine(){} 39 40 bool Detect() 41 { 42 if (m_horizontal){ 43 vector<double> edges; // 保存计算结果,每个扫描线的边缘Y坐标 44 edges.resize(m_right - m_left + 1); 45 46 // 逐列扫描边缘 47 for (int i = m_left, idx = 0; i <= m_right; ++i, idx++){ 48 edges[idx] = scanLineV(i, m_bottom, m_top); 49 } 50 if (!fitLine(m_left, edges, m_k, m_b)){ 51 return false; 52 } 53 m_x1 = m_left; 54 m_y1 = m_k * m_x1 + m_b; 55 m_x2 = m_right + 1; 56 m_y2 = m_k * m_x2 + m_b; 57 } 58 else{ 59 vector<double> edges; 60 edges.resize(m_top - m_bottom + 1); 61 62 // 逐行扫描边缘 63 for (int i = m_bottom, idx = 0; i <= m_top; ++i, idx++){ 64 edges[idx] = scanLineH(i, m_left, m_right); 65 } 66 if (!fitLine(m_bottom, edges, m_k, m_b)){ 67 return false; 68 } 69 m_y1 = m_bottom; 70 m_x1 = m_k * m_y1 + m_b; 71 m_y2 = m_top + 1; 72 m_x2 = m_k* m_y2 + m_b; 73 } 74 75 return true; 76 } 77 78 // 检测结果 79 double m_x1, m_y1, m_x2, m_y2; 80 double m_k, m_b; 81 82 private: 83 BYTE* m_image; 84 int m_width, m_height; // 图像尺寸 85 int m_stride; // 扫描行字节数 86 int m_left, m_right, m_bottom, m_top; // 探测区域 87 int m_halfEdge; // 边缘宽度的一半:像素 88 bool m_horizontal; // true:水平线;false:竖直线 89 bool m_blackToWhite; // true:左黑右白/下黑上白;false:左白右黑/下白上黑 90 vector<int> m_factor; // 对称边缘检测算子正半部分 91 92 const int c_productThresh = 100; // 边缘卷积阈值,小于该值则不认为是边缘 93 94 // 水平方向扫描检测边缘 95 double scanLineH(int y, int left, int right) 96 { 97 int maxL = 0, maxM = 0, maxR = 0, maxX; 98 int currL = 0, currM = 0, currR = 0; 99 BYTE* pData = m_image + m_stride * y + left; 100 for (int i = left; i <= right; ++i){ 101 currR = m_blackToWhite ? sumProductH(pData) : -sumProductH(pData); 102 if (currM >= currL && currM >= currR){ // 判定极值点 103 if (currM > maxM){ 104 maxL = currL; maxM = currM; maxR = currR; maxX = i - 1; 105 } 106 } 107 currL = currM; currM = currR; 108 pData++; 109 } 110 111 if (maxM < c_productThresh){ 112 return -1; 113 } 114 115 return maxX + (double)(maxM - maxL) / (maxM - maxL + maxM - maxR) - 0.5; 116 } 117 118 // 垂直方向扫描检测边缘 119 double scanLineV(int x, int bottom, int top) 120 { 121 int maxB = 0, maxM = 0, maxT = 0, maxY; 122 int currB = 0, currM = 0, currT = 0; 123 BYTE* pData = m_image + m_stride * bottom + x; 124 for (int i = bottom; i <= top; ++i){ 125 currT = m_blackToWhite ? sumProductV(pData) : -sumProductV(pData); 126 if (currM >= currB && currM >= currT){ // 判定极值点 127 if (currM > maxM){ 128 maxB= currB; maxM = currM; maxT = currT; maxY = i - 1; 129 } 130 } 131 currB = currM; currM = currT; 132 pData += m_stride; 133 } 134 135 if (maxM < c_productThresh){ 136 return -1; 137 } 138 139 return maxY + (double)(maxM - maxB) / (maxM - maxB + maxM - maxT) - 0.5; 140 } 141 142 // 计算水平扫描卷积值,pData对应算子中心 143 int sumProductH(const BYTE* pData) 144 { 145 int product = 0; 146 const BYTE* pDataN = pData - 1; 147 for (size_t i = 0; i < m_factor.size(); ++i){ 148 product += (m_factor[i] * pData[0] - m_factor[i] * pDataN[0]); 149 pData++; 150 pDataN--; 151 } 152 153 return product; 154 } 155 156 // 计算垂直扫描卷积值,pData对应算子中心 157 int sumProductV(const BYTE* pData) 158 { 159 int product = 0; 160 const BYTE* pDataN = pData - m_stride; 161 for (size_t i = 0; i < m_factor.size(); ++i){ 162 product += (m_factor[i] * pData[0] - m_factor[i] * pDataN[0]); 163 pData += m_stride; 164 pDataN -= m_stride; 165 } 166 167 return product; 168 } 169 170 // 直线拟合 171 bool fitLine(int start, vector<double> &points, double &k, double &b) 172 { 173 // 统计有效点数目 174 int validCount = 0; 175 for (size_t i = 0; i < points.size(); ++i){ 176 if (points[i] > 0){ 177 validCount++; 178 } 179 } 180 if (validCount < 2) 181 return false; 182 183 // 根据方差迭代 184 const int iterCount = 3; // 迭代次数 185 for (int iterator = 0; iterator < iterCount; ++iterator){ 186 double sigmaX = 0, sigmaY = 0, sigmaX2 = 0, sigmaXY = 0; 187 double X = start - 0.5; 188 for (size_t i = 0; i < points.size(); ++i){ 189 X += 1; 190 if (points[i] < 0){ 191 continue; 192 } 193 sigmaX += X; 194 sigmaY += points[i]; 195 sigmaXY += X * points[i]; 196 sigmaX2 += X * X; 197 } 198 double denominator = (validCount * sigmaX2 - sigmaX * sigmaX); 199 k = (validCount * sigmaXY - sigmaX * sigmaY) / denominator; 200 b = (sigmaX2 * sigmaY - sigmaX * sigmaXY) / denominator; 201 202 // 方差 203 double e = 0; 204 X = start - 0.5; 205 for (size_t i = 0; i < points.size(); ++i){ 206 X += 1; 207 if (points[i] < 0){ 208 continue; 209 } 210 e += (points[i] - k * X - b) * (points[i] - k * X - b); 211 } 212 e /= validCount; 213 if (e < 2){ 214 break; 215 } 216 e = sqrt(e); 217 218 // 剔除误差过大的点 219 X = start - 0.5; 220 for (size_t i = 0; i < points.size(); ++i){ 221 X += 1; 222 if (points[i] < 0){ 223 continue; 224 } 225 if (abs(points[i] - k * X - b) > e){ 226 points[i] = -1; 227 validCount--; 228 } 229 } 230 if (validCount < 2){ 231 break; 232 } 233 } 234 235 return true; 236 } 237 };