对图像的像素进行访问,可以实现空间增强,反色,大部分图像特效系列都是基于像素操作的。图像容器Mat是一个矩阵的形式,一般情况下是二维的。单通道灰度图一般存放的是<uchar>类型,其数据存放格式如下:
多通道的图像中,每列并列存放通道数量的子列,如RGB三通道彩色图:
注意通道的顺序为BGR。通常在内存足够大的情况下,图像的每一行是连续存放的,亦即在内存上图像的所有数据组成一个一维向量,这种情况下,在访问时将更快捷。可用成员函数isContinuous()来判断Mat图像在内存中是否为连续存储的。
清楚了图像在内存中的存储方式,下面对像素值操作:在只对深度为8bit字节型图像操作的前提下,导入一幅彩色图像,将其像素值变换为对255的补数。如原像素值为55,则变换为255-55=200。
使用一映射表来完成该转换:
1 uchar mapTable[256];//mapping table:mapTable[pixel_value_before]=255-pixel_value_before;2 for (int i = 0; i < 256; i++)3 mapTable[i] = 255 - i;
数组mapTable中装入的即是原像素值变换后的像素值。
可用如下方法对像素值进行操作:
1、指针的方式
1 //通过ptr和[]访问像素的值 2 void transformImageMethodOne(Mat& pSrcImg, const uchar* const table) 3 { 4 CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message 5 int nchannels = pSrcImg.channels(); 6 int nrows = pSrcImg.rows;//矩阵的行数 7 int ncols = pSrcImg.cols*nchannels;//矩阵的总列数=列数*nchannels 8 if (pSrcImg.isContinuous())//isContinuous()函数用于判断矩阵是否连续,若连续,相当于只需要遍历一个一维数组 9 {10 cout << "only one row!" << endl;11 ncols *= nrows;12 nrows = 1;//一维数组13 }14 //traverse pixel values15 for (int i = 0; i < nrows; i++)16 {17 uchar* ptr = pSrcImg.ptr(i);//获取行地址18 for (int j = 0; j < ncols; j++)19 ptr[j] = table[ptr[j]];//修改像素值20 }21 // return pSrcImg;22 }
如代码所示,我们获取每一行开始处的指针,然后遍历至该行末尾。如果矩阵是连续存储的,则只需请求一次指针即可。
2、或者,也可以借助Mat的成员data。
data会从Mat中返回指向矩阵的首地址。通过遍历data来扫描整个图像。具体操作如下:
1 //通过ptr和[]访问像素的值(使用到了Mat的成员data) 2 void transformImageMethodTwo(Mat& pSrcImg, const uchar* const table) 3 { 4 CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message 5 int nchannels = pSrcImg.channels(); 6 int nrows = pSrcImg.rows; 7 int ncols = pSrcImg.cols*nchannels; 8 //traver pixel values 9 uchar* ptr = pSrcImg.data;//指向矩阵的首地址10 for (int i = 0; i < ncols*nrows; i++)11 *ptr++ = table[*ptr];//*ptr++是先取出*p的值,然后让p++12 13 }
3、使用迭代器
使用迭代器的方法,仅仅需要获得图像矩阵的begin和end,然后从begin迭代至end,将操作符*添加至迭代指针前,即可访问当前指向的内容。
1 //使用迭代器访问像素的值 2 void transformImageMethodThree(Mat& pSrcImg, const uchar* const table) 3 { 4 CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message 5 const int nchannels = pSrcImg.channels(); 6 switch (nchannels) 7 { 8 case 1: 9 {10 MatIterator_it, end;11 for (it = pSrcImg.begin (), end = pSrcImg.end (); it != end; it++)12 *it = table[*it];13 break;14 }15 case 3:16 {17 MatIterator_ it, end;18 for (it = pSrcImg.begin (), end = pSrcImg.end (); it != end; it++)19 {20 (*it)[0] = table[(*it)[0]];21 (*it)[1] = table[(*it)[1]];22 (*it)[2] = table[(*it)[2]];23 }24 break;25 }26 default:break;27 }28 29 }
注意:这里对3通道的图像进行操作的时候,使用到了Vec3b。Vec3b作为一个对三元向量的数据结构,用在这里正好是能够表示RGB的三个分量。如果对于彩色图像,仍然使用uchar的话,则只能获得3通道中的B分量。
4、动态地址访问
这种方法在需要连续扫描所有点的应用场景时效率较低,因为它更适用于随机访问。这种方法最基本的用途是访问任意的某一行某一列:
1 //使用动态地址访问像素的值 2 void transformImageMethodFour(Mat& pSrcImg, const uchar* const table) 3 { 4 CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message 5 const int nchannels = pSrcImg.channels(); 6 const int nrows = pSrcImg.rows; 7 const int ncols = pSrcImg.cols; 8 switch (nchannels) 9 {10 case 1:11 {12 for (int i = 0; i < nrows;i++)13 for (int j = 0; j < ncols; j++)14 pSrcImg.at(i, j) = table[pSrcImg.at (i, j)];15 break;16 }17 case 3:18 {19 Mat_ _pSrcImg = pSrcImg;20 for (int i = 0; i < nrows;i++)21 for (int j = 0; j < ncols; j++)22 {23 _pSrcImg(i, j)[0] = table[_pSrcImg(i, j)[0]];24 _pSrcImg(i, j)[1] = table[_pSrcImg(i, j)[1]];25 _pSrcImg(i, j)[2] = table[_pSrcImg(i, j)[2]];26 }27 // pSrcImg = _pSrcImg;28 break;29 }30 default:break;31 }32 }
测试代码:
1 /* 2 @author:CodingMengmeng 3 @theme:read the image pixel values by Mat 4 @time:2017-3-16 23:06:40 5 @blog:http://www.cnblogs.com/codingmengmeng/ 6 */ 7 #include8 #include 9 using namespace std; 10 using namespace cv; 11 12 //通过ptr和[]访问像素的值 13 void transformImageMethodOne(Mat& pSrcImg, const uchar* const table) 14 { 15 CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message 16 int nchannels = pSrcImg.channels(); 17 int nrows = pSrcImg.rows;//矩阵的行数 18 int ncols = pSrcImg.cols*nchannels;//矩阵的总列数=列数*nchannels 19 if (pSrcImg.isContinuous())//isContinuous()函数用于判断矩阵是否连续,若连续,相当于只需要遍历一个一维数组 20 { 21 cout << "only one row!" << endl; 22 ncols *= nrows; 23 nrows = 1;//一维数组 24 } 25 //traverse pixel values 26 for (int i = 0; i < nrows; i++) 27 { 28 uchar* ptr = pSrcImg.ptr (i);//获取行地址 29 for (int j = 0; j < ncols; j++) 30 ptr[j] = table[ptr[j]];//修改像素值 31 } 32 // return pSrcImg; 33 } 34 //通过ptr和[]访问像素的值(使用到了Mat的成员data) 35 void transformImageMethodTwo(Mat& pSrcImg, const uchar* const table) 36 { 37 CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message 38 int nchannels = pSrcImg.channels(); 39 int nrows = pSrcImg.rows; 40 int ncols = pSrcImg.cols*nchannels; 41 //traver pixel values 42 uchar* ptr = pSrcImg.data;//指向矩阵的首地址 43 for (int i = 0; i < ncols*nrows; i++) 44 *ptr++ = table[*ptr];//*ptr++是先取出*p的值,然后让p++ 45 46 } 47 //使用迭代器访问像素的值 48 void transformImageMethodThree(Mat& pSrcImg, const uchar* const table) 49 { 50 CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message 51 const int nchannels = pSrcImg.channels(); 52 switch (nchannels) 53 { 54 case 1: 55 { 56 MatIterator_ it, end; 57 for (it = pSrcImg.begin (), end = pSrcImg.end (); it != end; it++) 58 *it = table[*it]; 59 break; 60 } 61 case 3: 62 { 63 MatIterator_ it, end; 64 for (it = pSrcImg.begin (), end = pSrcImg.end (); it != end; it++) 65 { 66 (*it)[0] = table[(*it)[0]]; 67 (*it)[1] = table[(*it)[1]]; 68 (*it)[2] = table[(*it)[2]]; 69 } 70 break; 71 } 72 default:break; 73 } 74 75 } 76 //使用动态地址访问像素的值 77 void transformImageMethodFour(Mat& pSrcImg, const uchar* const table) 78 { 79 CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message 80 const int nchannels = pSrcImg.channels(); 81 const int nrows = pSrcImg.rows; 82 const int ncols = pSrcImg.cols; 83 switch (nchannels) 84 { 85 case 1: 86 { 87 for (int i = 0; i < nrows;i++) 88 for (int j = 0; j < ncols; j++) 89 pSrcImg.at (i, j) = table[pSrcImg.at (i, j)]; 90 break; 91 } 92 case 3: 93 { 94 Mat_ _pSrcImg = pSrcImg; 95 for (int i = 0; i < nrows;i++) 96 for (int j = 0; j < ncols; j++) 97 { 98 _pSrcImg(i, j)[0] = table[_pSrcImg(i, j)[0]]; 99 _pSrcImg(i, j)[1] = table[_pSrcImg(i, j)[1]];100 _pSrcImg(i, j)[2] = table[_pSrcImg(i, j)[2]];101 }102 // pSrcImg = _pSrcImg;103 break;104 }105 default:break;106 }107 }108 int main(void)109 {110 string imgName = "Route66.jpg";111 Mat img = imread(imgName);112 Mat imgCopy1 = img.clone();113 Mat imgCopy2 = img.clone();114 Mat imgCopy3 = img.clone();115 Mat imgCopy4 = img.clone();116 uchar mapTable[256];//mapping table:mapTable[pixel_value_before]=255-pixel_value_before;117 for (int i = 0; i < 256; i++)118 mapTable[i] = 255 - i;119 imshow("SRCIMAGE", img);120 transformImageMethodOne(imgCopy1, mapTable);121 imshow("TRANSFORMIMAGE_USE_METHOD1", imgCopy1);122 transformImageMethodTwo(imgCopy2, mapTable);123 imshow("TRANSFORMIMAGE_USE_METHOD2", imgCopy2);124 transformImageMethodThree(imgCopy3, mapTable);125 imshow("TRANSFORMIMAGE_USE_METHOD3", imgCopy3);126 transformImageMethodFour(imgCopy4, mapTable);127 imshow("TRANSFORMIMAGE_USE_METHOD4", imgCopy4);128 waitKey(0);129 return 0;130 }
运行结果:
原图:
变换效果图:
以上。