机器视觉原生算法入门

本文以边缘检测为例,提供一个机器视觉原生算法的入门案例。效果如下图,左边是源图片,右边是检测结果:

      

基本思路:逐行扫描探测边缘;对每行的边缘位置做直线拟合;使用方差剔除缺损点。

边缘探测算法:使用类似【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 };

 

posted on 2020-09-09 20:48  朱迎春  阅读(482)  评论(0编辑  收藏  举报