图像处理指针

2018-08-13 15:14:43 Abaqus3_0 阅读数 157

1:Systen.AccessViolationException {"尝试读取或写入受保护的内存。这通常指示其他内存已损坏。"}

思考:指针越界,超出图像数组内存。查找图像指针变化循环,调整指针位置。

2:p[0]未将引用对象设置至实例

调试:第一轮调试循环没问题,第二轮循环出现该错误,说明中间对象引用错误,重点检查条件改变部分。

错误的赋值:Ptr =(byte*)(stride*k+3*k)会导致这个问题,byte*的*表示指针,而stride*k+3*k并没有表示特定地址,而是图像中的距离,  byte* temp = (byte*)unlock.Scan0.ToPointer();  Scan0方法是指针,指向图像的首地址。可强制转化为byte*类型。

注意几点,第一:  * Ptr表示指针,是图像内存位置的变化,Ptr[0]是每个图像内存的第一个byte。

                  第二:若指针指向错误的位置,可能出现1的错误,也可能出现2的错误。

3:若使用指针,当指针指向数据头的时候,在这一组数据里所有的位置都已经排序好,一个指针只能按照顺序逐个访问。如图像数据,使用指针获取图像头,(byte*  Ptr)这个 * 就是指针,数据按照 byte的格式排列完成。图像一个像素有三个byte,每次访问一个Ptr,就得到RGB的值,即R=Ptr[0], G=Ptr[1], B=Ptr[2]。Ptr++;就表示移动一个byte的距离,即处理一个R分量。

4:数据类型的转换,在图像处理中应当特别注意,例如double类型转为int类型,或者两个不同的数据类型相乘,其结果大不相同。在0-255灰度值的图像来说,影响还是很大的。

     4.1双精度型和整型相乘,返回双精度型,强制转化为整型是直接去掉小数点后的数值。

     4.2全部加完双精度在强制转换的值较为接近原始值,先强制转换在相加,数值可能变小,因为小数点后的数值全被省略。

2015-05-04 14:40:30 lxw907304340 阅读数 5609



本文主要通过彩色图象灰度化来介绍C#处理数字图像的3种方法,Bitmap类、BitmapData类和Graphics类是C#处理图像的的3个重要的类。

Bitmap只要用于处理由像素数据定义的图像的对象,主要方法和属性如下:

         GetPixel方法和SetPixel方法,获取和设置一个图像的指定像素的颜色。

         PixelFormat属性,返回图像的像素格式。

         Palette属性,获取或折纸图像所使用的颜色调色板。

         Height属性和Width属性,返回图像的高度和宽度。

         LockBits方法和UnlockBits方法,分别锁定和解锁系统内存中的位图像素。

BitmapData对象指定了位图的属性:

         Height属性,被锁定位图的高度。

         Width属性,被锁定位图的宽度。

         PixelFormat属性,数据的实际像素格式。

         Scan0属性,被锁定数组的首字节地址。

         Stride属性,步幅,也称扫描宽度。

彩色图象灰度化

24位彩色图象每个像素用3个字节表示,每个字节对应着R、G、B分量的亮度(红、绿、蓝)。当3个分量不想同时表现为灰度图像。下面有三种转换公式:

Gray(I,j)为转换后的灰度图像在(I,j)点出的灰度值。由于人眼对颜色的感应不同,有了下面的转换公式:

观察发现绿色所占比重最大,所以转换时直接使用G值作为转换结果:

图像处理的3种方法分别是:提取像素法、内存法和指针法,它们各自有各自的特点。

提取像素法

使用的是GDI+中的Bitmap.GetPixel和Bitmap.SetPixel方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (bitmap != null)
{
    newbitmap = bitmap.Clone() as Bitmap;
    Color pixel;
    int ret;
    for (int x = 0; x < newbitmap.Width; x++)
    {
        for (int y = 0; y < newbitmap.Height; y++)
        {
            pixel = newbitmap.GetPixel(x, y);
            ret = (int)(pixel.R * 0.299 + pixel.G * 0.587 + pixel.B * 0.114);
            newbitmap.SetPixel(x, y, Color.FromArgb(ret, ret, ret));
        }
    }
    pictureBox1.Image = newbitmap.Clone() as Image;
}

内存法

内存法是把图像数据直接复制到内存中,这样程序的运行速度就能大大提高了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if (bitmap != null)
{
    newbitmap = bitmap.Clone() as Bitmap;
    Rectangle rect = new Rectangle(0, 0, newbitmap.Width, newbitmap.Height);
    System.Drawing.Imaging.BitmapData bmpdata = newbitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, newbitmap.PixelFormat);
    IntPtr ptr = bmpdata.Scan0;
    int bytes = newbitmap.Width * newbitmap.Height * 3;
    byte[] rgbvalues = new byte[bytes];
    System.Runtime.InteropServices.Marshal.Copy(ptr, rgbvalues, 0, bytes);
    double colortemp = 0;
    for (int i = 0; i < rgbvalues.Length; i += 3)
    {
        colortemp = rgbvalues[i + 2] * 0.299 + rgbvalues[i + 1] * 0.587 + rgbvalues[i] * 0.114;
        rgbvalues[i] = rgbvalues[i + 1] = rgbvalues[i + 2] = (byte)colortemp;
    }
    System.Runtime.InteropServices.Marshal.Copy(rgbvalues, 0, ptr, bytes);
    newbitmap.UnlockBits(bmpdata);
    pictureBox1.Image = newbitmap.Clone() as Image;
}

指针法

这个方法和内存法相似,开始都是通过LockBits方法来获取位图的首地址,这个方法更简洁,直接用指针进行位图操作。所以对内存的操作需要在unsafe下进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
if (bitmap != null)
{
    newbitmap = bitmap.Clone() as Bitmap;
    Rectangle rect = new Rectangle(0, 0, newbitmap.Width, newbitmap.Height);
    System.Drawing.Imaging.BitmapData bmpdata = newbitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, newbitmap.PixelFormat); 
    byte temp;
    unsafe
    {
        byte* ptr = (byte*)(bmpdata.Scan0);
        for (int x = 0; x < bmpdata.Width; x++)
        {
            for (int y = 0; y < bmpdata.Height; y++)
            {
                temp = (byte)(0.299 * ptr[2] + 0.587 * ptr[1] + 0.114 * ptr[0]);
                ptr[0] = ptr[1] = ptr[2] = temp;
                ptr += 3;
            }
            ptr += bmpdata.Stride - bmpdata.Width * 3;
        }
    }
    newbitmap.UnlockBits(bmpdata);
    pictureBox1.Image = newbitmap.Clone() as Image;
}

3种方法的比较

比较一下可以得出结论,提取像素法比较简单,但是效率比较低;内存法效率有了很大的提高,但是代码比较复杂;指针法效率比内存法更高一些,但是不安全。综上比较结果内存法比较好,效率即高又能发挥C#安全的优点。

程序下载:http://pan.baidu.com/s/1bnEJtuf

2016-12-08 09:39:44 u014365862 阅读数 2001

我们在实际应用中对图像进行的操作,往往并不是将图像作为一个整体进行操作,而是对图像中的所有点或特殊点进行运算,所以遍历图像就显得很重要,如何高效的遍历图像是一个很值得探讨的问题。

一、遍历图像的4种方式:at<typename>(i,j)

Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。下面我们通过一个图像处理中的实际来说明它的用法。

在实际应用中,我们很多时候需要对图像降色彩,因为256*256*256实在太多了,在图像颜色聚类或彩色直方图时,我们需要用一些代表性的颜色代替丰富的色彩空间,我们的思路是将每个通道的256种颜色用64种代替,即将原来256种颜色划分64个颜色段,每个颜色段取中间的颜色值作为代表色。

 1 void colorReduce(Mat& image,int div)
 2 {
 3     for(int i=0;i<image.rows;i++)
 4     {
 5         for(int j=0;j<image.cols;j++)
 6         {
 7             image.at<Vec3b>(i,j)[0]=image.at<Vec3b>(i,j)[0]/div*div+div/2;
 8             image.at<Vec3b>(i,j)[1]=image.at<Vec3b>(i,j)[1]/div*div+div/2;
 9             image.at<Vec3b>(i,j)[2]=image.at<Vec3b>(i,j)[2]/div*div+div/2;
10         }
11     }
12 }

image

通过上面的例子我们可以看出,at方法取图像中的点的用法:

image.at<uchar>(i,j):取出灰度图像中i行j列的点。

image.at<Vec3b>(i,j)[k]:取出彩色图像中i行j列第k通道的颜色点。其中uchar,Vec3b都是图像像素值的类型,不要对Vec3b这种类型感觉害怕,其实在core里它是通过typedef Vec<T,N>来定义的,N代表元素的个数,T代表类型。

更简单一些的方法:OpenCV定义了一个Mat的模板子类为Mat_,它重载了operator()让我们可以更方便的取图像上的点。

Mat_<uchar> im=image;

im(i,j)=im(i,j)/div*div+div/2;

二、高效一点:用指针来遍历图像

上面的例程中可以看到,我们实际喜欢把原图传进函数内,但是在函数内我们对原图像进行了修改,而将原图作为一个结果输出,很多时候我们需要保留原图,这样我们需要一个原图的副本。

 1 void colorReduce(const Mat& image,Mat& outImage,int div)
 2 {
 3     // 创建与原图像等尺寸的图像
 4     outImage.create(image.size(),image.type());
 5     int nr=image.rows;
 6     // 将3通道转换为1通道
 7     int nl=image.cols*image.channels();
 8     for(int k=0;k<nr;k++)
 9     {
10         // 每一行图像的指针
11         const uchar* inData=image.ptr<uchar>(k);
12         uchar* outData=outImage.ptr<uchar>(k);
13         for(int i=0;i<nl;i++)
14         {
15             outData[i]=inData[i]/div*div+div/2;
16         }
17     }
18 }

从上面的例子中可以看出,取出图像中第i行数据的指针:image.ptr<uchar>(i)。

值得说明的是:程序中将三通道的数据转换为1通道,在建立在每一行数据元素之间在内存里是连续存储的,每个像素三通道像素按顺序存储。也就是一幅图像数据最开始的三个值,是最左上角的那像素的三个通道的值。

但是这种用法不能用在行与行之间,因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。但是我们可以申明一个连续的空间来存储图像,这个话题引入下面最为高效的遍历图像的机制。

三、更高效的方法

上面已经提到过了,一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行。

 1 void colorReduce(const Mat& image,Mat& outImage,int div)
 2 {
 3     int nr=image.rows;
 4     int nc=image.cols;
 5     outImage.create(image.size(),image.type());
 6     if(image.isContinuous()&&outImage.isContinuous())
 7     {
 8         nr=1;
 9         nc=nc*image.rows*image.channels();
10     }
11     for(int i=0;i<nr;i++)
12     {
13         const uchar* inData=image.ptr<uchar>(i);
14         uchar* outData=outImage.ptr<uchar>(i);
15         for(int j=0;j<nc;j++)
16         {
17             *outData++=*inData++/div*div+div/2;
18         }
19     }
20 }

用指针除了用上面的方法外,还可以用指针来索引固定位置的像素:

image.step返回图像一行像素元素的个数(包括空白元素),image.elemSize()返回一个图像像素的大小。

&image.at<uchar>(i,j)=image.data+i*image.step+j*image.elemSize();

四、还有吗?用迭代器来遍历。

下面的方法可以让我们来为图像中的像素声明一个迭代器:

MatIterator_<Vec3b> it;

Mat_<Vec3b>::iterator it;

如果迭代器指向一个const图像,则可以用下面的声明:

MatConstIterator<Vec3b> it; 或者

Mat_<Vec3b>::const_iterator it;

下面我们用迭代器来简化上面的colorReduce程序:

 1 void colorReduce(const Mat& image,Mat& outImage,int div)
 2 {
 3     outImage.create(image.size(),image.type());
 4     MatConstIterator_<Vec3b> it_in=image.begin<Vec3b>();
 5     MatConstIterator_<Vec3b> itend_in=image.end<Vec3b>();
 6     MatIterator_<Vec3b> it_out=outImage.begin<Vec3b>();
 7     MatIterator_<Vec3b> itend_out=outImage.end<Vec3b>();
 8     while(it_in!=itend_in)
 9     {
10         (*it_out)[0]=(*it_in)[0]/div*div+div/2;
11         (*it_out)[1]=(*it_in)[1]/div*div+div/2;
12         (*it_out)[2]=(*it_in)[2]/div*div+div/2;
13         it_in++;
14         it_out++;
15     }
16 }

如果你想从第二行开始,则可以从image.begin<Vec3b>()+image.rows开始。

上面4种方法中,第3种方法的效率最高!

五、图像的邻域操作

很多时候,我们对图像处理时,要考虑它的邻域,比如3*3是我们常用的,这在图像滤波、去噪中最为常见,下面我们介绍如果在一次图像遍历过程中进行邻域的运算。

下面我们进行一个简单的滤波操作,滤波算子为[0 –1 0;-1 5 –1;0 –1 0]。

它可以让图像变得尖锐,而边缘更加突出。核心公式即:sharp(i.j)=5*image(i,j)-image(i-1,j)-image(i+1,j

)-image(i,j-1)-image(i,j+1)。

 1 void ImgFilter2d(const Mat &image,Mat& result)
 2 {
 3     result.create(image.size(),image.type());
 4     int nr=image.rows;
 5     int nc=image.cols*image.channels();
 6     for(int i=1;i<nr-1;i++)
 7     {
 8         const uchar* up_line=image.ptr<uchar>(i-1);//指向上一行
 9         const uchar* mid_line=image.ptr<uchar>(i);//当前行
10         const uchar* down_line=image.ptr<uchar>(i+1);//下一行
11         uchar* cur_line=result.ptr<uchar>(i);
12         for(int j=1;j<nc-1;j++)
13         {
14             cur_line[j]=saturate_cast<uchar>(5*mid_line[j]-mid_line[j-1]-mid_line[j+1]-
15                 up_line[j]-down_line[j]);
16         }
17     }
18     // 把图像边缘像素设置为0
19     result.row(0).setTo(Scalar(0));
20     result.row(result.rows-1).setTo(Scalar(0));
21     result.col(0).setTo(Scalar(0));
22     result.col(result.cols-1).setTo(Scalar(0));
23 }

image

上面的程序有以下几点需要说明:

1,staturate_cast<typename>是一个类型转换函数,程序里是为了确保运算结果还在uchar范围内。

2,row和col方法返回图像中的某些行或列,返回值是一个Mat。

3,setTo方法将Mat对像中的点设置为一个值,Scalar(n)为一个灰度值,Scalar(a,b,c)为一个彩色值。

六、图像的算术运算

Mat类把很多算数操作符都进行了重载,让它们来符合矩阵的一些运算,如果+、-、点乘等。

下面我们来看看用位操作和基本算术运算来完成本文中的colorReduce程序,它更简单,更高效。

将256种灰度阶降到64位其实是抛弃了二进制最后面的4位,所以我们可以用位操作来做这一步处理。

首先我们计算2^8降到2^n中的n:int n=static_cast<int>(log(static_cast<double>(div))/log(2.0));

然后可以得到mask,mask=0xFF<<n;

用下面简直的语句就可以得到我们想要的结果:

result=(image&Scalar(mask,mask,mask))+Scalar(div/2,div/2,div/2);

很多时候我们需要对图像的一个通信单独进行操作,比如在HSV色彩模式下,我们就经常把3个通道分开考虑。

1 vector<Mat> planes;
2 // 将image分为三个通道图像存储在planes中
3 split(image,planes);
4 planes[0]+=image2;
5 // 将planes中三幅图像合为一个三通道图像
6 merge(planes,result);

2016-12-03 10:39:58 qq_26751117 阅读数 0

目标:

  1. 初步掌握visual studio 2013 中控制台c程序的建立。
  2. 结合课本讲解指针概念;指针上机练习。
  3. 讲解动态分配和释放。 malloc/free.
  4. 一维指针动态分配和释放练习。
  5. 二维指针概念讲解。二维指针动态分配和释放练习。


作业:

矩阵乘法

  1. 用户输入矩阵1行数、列数,然后提示用户输入各元素;
  2. 用户输入矩阵2行数、列数,然后提示用户输入各元素;
  3. 输入后计算相乘结果矩阵。


要求:

  1. 自己安装Visual studio 2013. 从微软官网上下载visual studio 2013 (Community edition),然后申请序列号(免费),安装。
  2. 建立控制台应用程序。(上网查怎么建立)
  3. 使用二维指针及动态分配,完成作业。 (参考:数值算法大全,4个美国人写的,第2版或第3版的引言部分)
#include 
#include 
#include 
using namespace std;

int main(void)
{
	int n, m, l;
	int **a = NULL, **b = NULL, **result = NULL;
	int i, j, k;

	cout<<"Please enter the rows and columns of the first matix:";	/*输入矩阵行列数*/
	cin >> n >> m;
	cout<<"Please enter the columns of the second matix:";
	cin >> l;

	if ((a = (int**)malloc(n*sizeof(int*))) == NULL)	/*建立矩阵A*/
	{
		cout << "error!";
		exit(1);
	}
	for (i = 0; i < n; i++)
	{
		if ((a[i] = (int*)malloc(m*sizeof(int))) == NULL)
		{
			cout << "error!";
			exit(1);
		}
	}

	if ((b = (int**)malloc(m*sizeof(int*))) == NULL)	/*建立矩阵B*/
	{
		cout << "error!";
		exit(1);
	}
	for (i = 0; i < m; i++)
	{
		if ((b[i] = (int*)malloc(l*sizeof(int))) == NULL)
		{
			cout << "error!";
			exit(1);
		}
	}

	if ((result = (int**)malloc(m*sizeof(int*))) == NULL)	/*建立矩阵RESULT*/
	{
		cout << "error!";
		exit(1);
	}
	for (i = 0; i < n; i++)
	{
		if ((result[i] = (int*)malloc(l*sizeof(int))) == NULL)
		{
			cout << "error!";
			exit(1);
		}
	}

	cout << "Please enter the elements of the first matix:" << endl;	/*输入AB矩阵的元素*/
	for (i = 0; i < n; i++)
		for (j = 0; j < m; j++)
			cin >> a[i][j];
	cout << "Please enter the elements of the second matix:" << endl;
	for (i = 0; i < m; i++)
		for (j = 0; j < l; j++)
			cin >> b[i][j];

	for (i = 0; i < n; i++)		/*数组RESULT清零*/
		for (j = 0; j < l; j++)
			result[i][j] = 0;
	
	for (i = 0; i < n; i++)
	{
		for (j = 0; j < l; j++)
		{
			for (k = 0; k < m; k++)
				result[i][j] += a[i][k] * b[k][j];
			cout << result[i][j]<<'\t';
		}
		cout << endl;
	}
			
	for (i = 0; i < n; i++)		/*释放空间*/
	{
		free(a[i]);
		free(result[i]);
	}
	free(a);
	a = NULL;
	free(result);
	result = NULL;
	for (i = 0; i < m; i++)
		free(b[i]);
	free(b);
	b = NULL;

	system("pause");
	return 0;
}