二值图像轮廓跟踪用于提取二值图像中的目标区域,以便对目标区域做进一步处理,如:区域填充,计算轮廓长度、面积、重心,特征提取和图像识别等。我们可以采用链码和线段表两种不同的方法来描述轮廓。链码方式描述轮廓需要记录轮廓的起点和轮廓上每一点相对于前一点的链码值列表。以8-领域链码为例,首先按照从左至右,从上至下的顺序查找轮廓起点;再按照右、右下、下、左下的顺序查找第二个轮廓点;然后按照右、右下、下、左下、左、左上、上、上右的顺序查找其它轮廓点,直到找到轮廓起点为止。 由于事先无法确定轮廓点数量,无法事先分配合适的内存空间,因此可以使用vector来动态管理内存,从而简化程序设计。
#include <vector> using namespace std; typedef std::vector<int> ivecChainCode; // 轮廓跟踪 // 1. pImageData 图像数据 // 2. nWidth 图像宽度 // 3. nHeight 图像高度 // 4. nWidthStep 图像行大小 // 5. pStart 起始点 // 6. pChainCode 链码表 BOOL TracingContour(unsigned char *pImageData, int nWidth, int nHeight, int nWidthStep, POINT *pStart, ivecChainCode *pChainCode) { int i = 0; int j = 0; int k = 0; int x = 0; int y = 0; unsigned char *pLine = NULL; BOOL bTracing = FALSE; POINT ptCurrent = { 0, 0 }; POINT ptTemp = { 0, 0 }; const POINT ptOffset[8] = { { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 }, { 0, -1 }, { 1, -1 } }; // 清空起始点与链码表 pStart->x = 0; pStart->y = 0; pChainCode->clear(); // 轮廓起点 for (y = 0; y < nHeight; y++) { pLine = pImageData + nWidthStep * y; for (x = 0; x < nWidth; x++) { if (pLine[x] == 0xFF) { bTracing = TRUE; pStart->x = x; pStart->y = y; ptCurrent.x = x; ptCurrent.y = y; } } } // 轮廓跟踪 while (bTracing) { bTracing = FALSE; for (i = 0; i < 8; i++, k++) { k &= 0x07; x = ptCurrent.x + ptOffset[k].x; y = ptCurrent.y + ptOffset[k].y; if (x >= 0 && x < nWidth && y >= 0 && y < nHeight) { // 判断是否为轮廓点 if (pImageData[nWidthStep * y + x] == 0xFF) { for (j = 0; j < 8; j += 2) { ptTemp.x = x + ptOffset[j].x; ptTemp.y = y + ptOffset[j].y; if (ptTemp.x >= 0 && ptTemp.x < nWidth && ptTemp.y >= 0 && ptTemp.y < nHeight) { if (pImageData[nWidthStep * ptTemp.y + ptTemp.x] == 0) { bTracing = TRUE; ptCurrent.x = x; ptCurrent.y = y; pChainCode->push_back(k); break; } } } } } if (bTracing) { // 如果当前点为轮廓起点 if (pStart->x == ptCurrent.x && pStart->y == ptCurrent.y) { // 则跟踪完毕 bTracing = FALSE; } break; } } k += 0x06; } return TRUE; }
// 轮廓绘制 // 1. pImageData 图像数据 // 2. nWidth 图像宽度 // 3. nHeight 图像高度 // 4. nWidthStep 图像行大小 // 5. ptStart 起始点 // 6. ChainCode 链码表 BOOL DrawContour(unsigned char *pImageData, int nWidth, int nHeight, int nWidthStep, POINT ptStart, ivecChainCode ChainCode) { POINT ptCurrent = { 0, 0 }; const POINT ptOffset[8] = { { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 }, { 0, -1 }, { 1, -1 } }; // 清空图像 memset(pImageData, 0, nWidthStep * nHeight); // 轮廓绘制 ptCurrent.x = ptStart.x; ptCurrent.y = ptStart.y; pImageData[nWidthStep * ptCurrent.y + ptCurrent.x] = 0xFF; for (ivecChainCode::iterator I = ChainCode.begin(); I != ChainCode.end(); i++) { ptCurrent.x += ptOffset[*i].x; ptCurrent.y += ptOffset[*i].y; pImageData[nWidthStep * ptCurrent.y + ptCurrent.x] = 0xFF; } return TRUE; }
轮廓跟踪效果:
|
用户377235 2012-12-4 21:13
轮廓跟踪的很巧妙,不会因为边缘的一些特别情况而提前终止。想问一下博主,while循环最后的那个 k += 0x06; 是什么道理啊?还望指教,多谢!