Opencv Line函数学习
看OpenCV时看到自己不常用的Line函数时不禁想到之前每次对图像进行处理都是在已有图片上进行空域或者频域操作完成的,而如果要在图片上画框rect或者画线line是如何做到的呢,既然Line函数并没用可以选择的算法类型,那么用的是DDA或者Bresenham还是哪种方法呢(虽然不可能是DDA哈哈)?出于好奇心去看了OpenCV的源代码。
OpenCV源代码Line函数定义:
static void
Line( Mat& img, Point pt1, Point pt2,
const void* _color, int connectivity = 8 )
{
//必须为8连通或者4连通
if( connectivity == 0 )
connectivity = 8;
else if( connectivity == 1 )
connectivity = 4;
//迭代类
LineIterator iterator(img, pt1, pt2, connectivity, true);
int i, count = iterator.count;//p1,p2点间的像素总数
int pix_size = (int)img.elemSize();//每个元素的数据大小
const uchar* color = (const uchar*)_color;
for( i = 0; i < count; i++, ++iterator )
{
uchar* ptr = *iterator;
if( pix_size == 1 )
ptr[0] = color[0];
else if( pix_size == 3 )
{
ptr[0] = color[0];
ptr[1] = color[1];
ptr[2] = color[2];
}
else
memcpy( *iterator, color, pix_size );
}
}
嘛,说白了,具体操作还是在LineIterator 定义的类中,官方是这么说的:
The class is used to iterate over all the pixels on the raster line segment connecting two specified points.
The class LineIterator is used to get each pixel of a raster line. It can be treated as versatile implementation of the Bresenham algorithm where you can stop at each pixel and do some extra processing.
这里已经解开谜题了,就是说他就是Bresenham算法的实现,可以在像素上进行一些额外的操作比如异或,那么我们来看看这个LineIterator 的重载里干了什么:
LineIterator::LineIterator(const Mat& img, Point pt1, Point pt2,
int connectivity, bool left_to_right)
{
count = -1;
CV_Assert( connectivity == 8 || connectivity == 4 );
//错误操作处理
if( (unsigned)pt1.x >= (unsigned)(img.cols) ||
(unsigned)pt2.x >= (unsigned)(img.cols) ||
(unsigned)pt1.y >= (unsigned)(img.rows) ||
(unsigned)pt2.y >= (unsigned)(img.rows) )
{
if( !clipLine( img.size(), pt1, pt2 ) )
{
ptr = img.data;
err = plusDelta = minusDelta = plusStep = minusStep = count = 0;
ptr0 = 0;
step = 0;
elemSize = 0;
return;
}
}
size_t bt_pix0 = img.elemSize(), bt_pix = bt_pix0;
size_t istep = img.step;
int dx = pt2.x - pt1.x;
int dy = pt2.y - pt1.y;
int s = dx < 0 ? -1 : 0;//绝对值操作
//此处即为官方解释中的 If leftToRight=true, then the iteration is always done from the left-most point to the right most, not to depend on the ordering of pt1 and pt2 parameters的意思
if( left_to_right )
{
dx = (dx ^ s) - s;
dy = (dy ^ s) - s;
pt1.x ^= (pt1.x ^ pt2.x) & s;
pt1.y ^= (pt1.y ^ pt2.y) & s;
}
else
{
dx = (dx ^ s) - s;
bt_pix = (bt_pix ^ s) - s;
}
//像素位置
ptr = (uchar*)(img.data + pt1.y * istep + pt1.x * bt_pix0);
s = dy < 0 ? -1 : 0;
dy = (dy ^ s) - s;
istep = (istep ^ s) - s;
s = dy > dx ? -1 : 0;
//交换dx,dy的值
/* conditional swaps */
dx ^= dy & s;
dy ^= dx & s;
dx ^= dy & s;
bt_pix ^= istep & s;
istep ^= bt_pix & s;
bt_pix ^= istep & s;
//类数据初始化
if( connectivity == 8 )
{
assert( dx >= 0 && dy >= 0 );
err = dx - (dy + dy);
plusDelta = dx + dx;
minusDelta = -(dy + dy);
plusStep = (int)istep;
minusStep = (int)bt_pix;
count = dx + 1;
}
else /* connectivity == 4 */
{
assert( dx >= 0 && dy >= 0 );
err = 0;
plusDelta = (dx + dx) + (dy + dy);
minusDelta = -(dy + dy);
plusStep = (int)(istep - bt_pix);
minusStep = (int)bt_pix;
count = dx + dy + 1;
}
this->ptr0 = img.ptr();
this->step = (int)img.step;
this->elemSize = (int)bt_pix0;
}
其实从上可以看出LineIterator 仅仅是给了Line函数prt值,而prt就是所画直线的位置的像素数据,信息被存在LineIterator 迭代中,用++()进行处理,即:
for( i = 0; i < count; i++, ++iterator )
{
uchar* ptr = *iterator;
if( pix_size == 1 )
ptr[0] = color[0];
else if( pix_size == 3 )
{
ptr[0] = color[0];
ptr[1] = color[1];
ptr[2] = color[2];
}
else
memcpy( *iterator, color, pix_size );
}
好了,最后看了代码后又学到了一直效率较高方便的swap函数交换方法,其实上面的代码中已包含,即上面的异或运算,在此再码一下:
void swap(int a, int b)
{
a = a ^ b;
b = a ^ b;
a = a ^ b;
}