iOS图像处理(位图图像原图修改)
2017-03-07 15:47
597 查看
总结一下最近看到的关于图像处理的知识
当前流行的图片处理方式有
1:位图图像原图修改
2:使用Core Graphics库
3:使用Core Image库
4:使用GPUImage库的第三部分
首先讲解一下32位RGBA模式
如同它的名字一样,32位RGBA模式会将一个颜色值存储在32位,或者4个字节中。每一个字节存储一个部分或者一个颜色通道。这4个部分分别是:
~ R代表红色
~ G代表绿色
~ B代表蓝色
~ A代表透明度
我们发现每8位代表一个颜色
alpha通道与其它的不同。你可以把它当成透明的东西,就像UIView的alpah属性。
透明颜色意味着没有任何的颜色,除非在它的后面有另外一种颜色;它的主要功能就是要告诉图像处理这个像素的透明度是多少,于是,就会有多少颜色值穿透过它而显示出来。
颜色空间
使用RGB模式表示颜色是颜色空间的一个例子。它只是众多存储颜色方法中的一种。另外一种颜色空间是灰阶空间。像它的名字一样,所有的图形都只有黑和白,只需要保存一个值来表示这种颜色。
另外两种比较常见的颜色空间是HSV和YUV。
HSV,使用色调,饱和度和亮度来直观的存储颜色值。你可以把这三个部分这样来看:
·色调就是颜色
·饱和度就是这个颜色有多么的饱满
·值就是颜色的亮度有多亮
YUV是另外一种常见的颜色空间,电视机使用的就是这种方式。
最开始的时候,电视机只有灰阶空间一种颜色通道。后来,当彩色电影出现后,就有了2种通道。当然,如果你想在本教程中使用YUV,那么你需要去研究更多关于YUV和其它颜色空间的相关知识。
NOTE:同样的颜色空间,你也可以使用不同的方法表示颜色。比如16位RGB模式,可以使用5个字节存储R,6个字节存储G,5个字节存储B。
为什么用6个字节存储绿色,5个字节存储蓝色?这是一个有意思的问题,答案就是因为眼球。人类的眼球对绿色比较敏感,所以人类的眼球更空间分辨出绿色的颜色值变化。
坐标系统
既然一个图形是由像素构成的平面地图,那么图像的原点需要说明一下。通常原点在图像的左上角,Y轴向下;或者原点在图像的左下,Y轴向上。
没有固定的坐标系统,苹果在不同的地方可能会使用不同的坐标系。
目前,UIImage和UIView使用的是左上原点坐标,Core Image和Core Graphics使用的是左下原点坐标。这个概念很重要,当你遇到图像绘制倒立问题的时候你就知道了。
图形压缩
这是在你开始编写代码前的最后一个需要了解的概念了!原图的每一个像素都被存储在各自的内存中。
如果你使用一张8像素的图形做运算,它将会消耗810^6像素4比特/像素=32兆字节内存。关注一下数据!
这就是为什么会出现jpeg,png和其它图形格式的原因。这些都是图形压缩格式。
当GPU在绘制图像的时候,会使用大量内存把图像的原始尺寸进行解压缩。如果你的程序占用了过多的内存,那么操作系统会将进程杀死(程序崩溃)。所以请确定你的程序使用较大的图像进行过测试。
NOTE:当你绘制图像的时候,设备的GPU会进行解码并将它显示在屏幕。为了访问本地数据,你需要一份像素的复制,就像刚才做的那样
此时此刻,pixels存储着图像的所有像素信息。下面的几行代码会对pixels进行遍历,并打印:
原图修改
在本方法中,你会遍历每一个像素,就像之前做的那个,但这次,将会对每个像素进行新的赋值。
这种方法的优点是容易实现和理解;缺点就是扫描大的图形和效果的时候会更复杂,不精简。
根据前面所介绍的方法,将图片的inputImage绘制出来,并且返回在桌面上
下一步,添加下面的代码到注释语句 Do some processing here的下面来进行混合:
这部分有2点需要说明:
1:你将幽灵图像的每一个像素的透明通道都乘以了0.5,使它成为半透明状态。然后将它混合到图像中像之前讨论的那样。
2:clamping部分将每个颜色的值范围进行限定到0到255之间,虽然一般情况下值不会越界。但是,大多数情况下需要进行这种限定防止发生意外的错误输出。
最后一句,将返回值替换
黑白颜色
最后一种效果。尝试自己实现黑白颜色效果。为了做到这点,你需要把每一个像素的红色,绿色,蓝色通道的值设定成三个通道原始颜色值的平均值,就像开始的时候输出幽灵图像所有像素亮度值那样。
在注释语句// create a new UIImage前添加上一步的代码 。
最后的一步就是清除内存。ARC不能代替你对CGImageRefs和CGContexts进行管理。添加如下代码到返回语句之前。
恭喜!你已经完成了自己的第一个图像处理程序。你可以在这里下载该工程的源代码。
raywenderlich.com/wp-content/uploads/2014/03/SpookCam-Starter.zip
还不错吧?你可以尝试修改一下循环中的代码创建自己想要的效果,尝试下实现下面的效果:
·尝试调换图像的红色和蓝色通道值
·提高图像的亮度10%
·作为进一步的挑战,尝试只使用基于像素的方法缩放幽灵的图像,下面是步骤:
1:使用幽灵图像的尺寸大小创建一个新的CGContext。
2:在原图像中得到你想要的并赋值到新的缓存图像中。
3:附加,尝试在像素之前进行计算并插入相似值像素点。如果你可以在四个像素间进行插入,你自己就已经实现 Bilinear scaling(双线性插值法)了
当前流行的图片处理方式有
1:位图图像原图修改
2:使用Core Graphics库
3:使用Core Image库
4:使用GPUImage库的第三部分
首先讲解一下32位RGBA模式
如同它的名字一样,32位RGBA模式会将一个颜色值存储在32位,或者4个字节中。每一个字节存储一个部分或者一个颜色通道。这4个部分分别是:
~ R代表红色
~ G代表绿色
~ B代表蓝色
~ A代表透明度
我们发现每8位代表一个颜色
alpha通道与其它的不同。你可以把它当成透明的东西,就像UIView的alpah属性。
透明颜色意味着没有任何的颜色,除非在它的后面有另外一种颜色;它的主要功能就是要告诉图像处理这个像素的透明度是多少,于是,就会有多少颜色值穿透过它而显示出来。
颜色空间
使用RGB模式表示颜色是颜色空间的一个例子。它只是众多存储颜色方法中的一种。另外一种颜色空间是灰阶空间。像它的名字一样,所有的图形都只有黑和白,只需要保存一个值来表示这种颜色。
另外两种比较常见的颜色空间是HSV和YUV。
HSV,使用色调,饱和度和亮度来直观的存储颜色值。你可以把这三个部分这样来看:
·色调就是颜色
·饱和度就是这个颜色有多么的饱满
·值就是颜色的亮度有多亮
YUV是另外一种常见的颜色空间,电视机使用的就是这种方式。
最开始的时候,电视机只有灰阶空间一种颜色通道。后来,当彩色电影出现后,就有了2种通道。当然,如果你想在本教程中使用YUV,那么你需要去研究更多关于YUV和其它颜色空间的相关知识。
NOTE:同样的颜色空间,你也可以使用不同的方法表示颜色。比如16位RGB模式,可以使用5个字节存储R,6个字节存储G,5个字节存储B。
为什么用6个字节存储绿色,5个字节存储蓝色?这是一个有意思的问题,答案就是因为眼球。人类的眼球对绿色比较敏感,所以人类的眼球更空间分辨出绿色的颜色值变化。
坐标系统
既然一个图形是由像素构成的平面地图,那么图像的原点需要说明一下。通常原点在图像的左上角,Y轴向下;或者原点在图像的左下,Y轴向上。
没有固定的坐标系统,苹果在不同的地方可能会使用不同的坐标系。
目前,UIImage和UIView使用的是左上原点坐标,Core Image和Core Graphics使用的是左下原点坐标。这个概念很重要,当你遇到图像绘制倒立问题的时候你就知道了。
图形压缩
这是在你开始编写代码前的最后一个需要了解的概念了!原图的每一个像素都被存储在各自的内存中。
如果你使用一张8像素的图形做运算,它将会消耗810^6像素4比特/像素=32兆字节内存。关注一下数据!
这就是为什么会出现jpeg,png和其它图形格式的原因。这些都是图形压缩格式。
当GPU在绘制图像的时候,会使用大量内存把图像的原始尺寸进行解压缩。如果你的程序占用了过多的内存,那么操作系统会将进程杀死(程序崩溃)。所以请确定你的程序使用较大的图像进行过测试。
UIImage * image = [UIImage imageNamed:@"back.png"]; // 1. /* 把UIImage对象转换为需要的核心图形库调用的CGImage对象。同时,得到图形的宽度和高度。 */ CGImageRef inputCGImage = [image CGImage]; NSUInteger width = CGImageGetWidth(inputCGImage); NSUInteger height = CGImageGetHeight(inputCGImage); // 2. /* 由于使用的是32位RGB颜色空间模式,你需要定义一些参数bytesPerPixel(每个像素大小),bitsPerComponent(每个颜色通道大小),计算出bytesPerRow(每行多大)。数组存储像素的值 */ NSUInteger bytesPerPixel = 4; NSUInteger bytesPerRow = bytesPerPixel * width; NSUInteger bitsPerComponent = 8; UInt32 * pixels; pixels = (UInt32 *) calloc(height * width, sizeof(UInt32)); // 3. /* 创建一个RGB模式的颜色空间CGColorSpace和一个容器CGBitmapContext,将像素指针参数传递到容器中缓存进行存储。 */ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(pixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); // 4. /* 把缓存中的图形绘制在显示器上,像素的填充格式有创建context的时候进行指定的 */ CGContextDrawImage(context, CGRectMake(0, 0, width, height), inputCGImage); // 5. Cleanup /* 清除colorSpace和context */ CGColorSpaceRelease(colorSpace); CGContextRelease(context);
NOTE:当你绘制图像的时候,设备的GPU会进行解码并将它显示在屏幕。为了访问本地数据,你需要一份像素的复制,就像刚才做的那样
此时此刻,pixels存储着图像的所有像素信息。下面的几行代码会对pixels进行遍历,并打印:
// 1. /* 定义一些简单处理32位像素的宏。为了得到红色通道的值,你需要得到前8位,以此类推 */ #define Mask8(x) ( (x) & 0xFF ) #define R(x) ( Mask8(x) ) #define G(x) ( Mask8(x >> 8 ) ) #define B(x) ( Mask8(x >> 16) ) NSLog(@"Brightness of image:"); // 2. /* 定义一个指向第一个像素的指针,并使用2个for循环来遍历像素 */ UInt32 * currentPixel = pixels; for (NSUInteger j = 0; j < height; j++) { for (NSUInteger i = 0; i < width; i++) { // 3. /* 得到当前像素的值赋值给currentPixel并把它的亮度值打印出来 */ UInt32 color = *currentPixel; printf("%3.0f ",(R(color)+G(color)+B(color))/3.0); // 4. /* 增加currentPixel的值,使它指向下一个像素。如果你对指针的运算比较生疏,记住这个:currentPixel是一个指向UInt32的变量,当你把它加1后,他就会向前移动4字节(32位),然后指向下一个像素值 提示:还有一种非正统的方法就是把currentPiexl声明为一个指向8字节的类型的指针,比如char。这种方法,你每增加1,你将会移动图形的下一个颜色通道。与它进行位移运算,你会得到颜色通道的8位数值。 */ currentPixel++; } printf("\n"); }
原图修改
在本方法中,你会遍历每一个像素,就像之前做的那个,但这次,将会对每个像素进行新的赋值。
这种方法的优点是容易实现和理解;缺点就是扫描大的图形和效果的时候会更复杂,不精简。
根据前面所介绍的方法,将图片的inputImage绘制出来,并且返回在桌面上
#pragma mark - Private #define Mask8(x) ( (x) & 0xFF ) #define R(x) ( Mask8(x) ) #define G(x) ( Mask8(x >> 8 ) ) #define B(x) ( Mask8(x >> 16) ) #define A(x) ( Mask8(x >> 24) ) #define RGBAMake(r, g, b, a) ( Mask8(r) | Mask8(g) << 8 | Mask8(b) << 16 | Mask8(a) << 24 ) - (UIImage *)processUsingPixels:(UIImage*)inputImage { // 1. Get the raw pixels of the image UInt32 * inputPixels; CGImageRef inputCGImage = [inputImage CGImage]; NSUInteger inputWidth = CGImageGetWidth(inputCGImage); NSUInteger inputHeight = CGImageGetHeight(inputCGImage); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); NSUInteger bytesPerPixel = 4; NSUInteger bitsPerComponent = 8; NSUInteger inputBytesPerRow = bytesPerPixel * inputWidth; inputPixels = (UInt32 *)calloc(inputHeight * inputWidth, sizeof(UInt32)); CGContextRef context = CGBitmapContextCreate(inputPixels, inputWidth, inputHeight, bitsPerComponent, inputBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGContextDrawImage(context, CGRectMake(0, 0, inputWidth, inputHeight), inputCGImage); UIImage * ghostImage = [UIImage imageNamed:@"ghost"]; CGImageRef ghostCGImage = [ghostImage CGImage]; //把ghost图片宽度缩小25%,并把它的圆点设定在ghostOrigin上(在inputImage的位置) CGFloat ghostImageAspectRatio = ghostImage.size.width / ghostImage.size.height; NSInteger targetGhostWidth = inputWidth * 0.25; CGSize ghostSize = CGSizeMake(targetGhostWidth,targetGhostWidth / ghostImageAspectRatio); CGPoint ghostOrigin = CGPointMake(inputWidth * 0.5,inputHeight * 0.2); //创建ghost图片的缓存图 NSUInteger ghostBytesPerRow = bytesPerPixel * ghostSize.width; UInt32 * ghostPixels = (UInt32 *)calloc(ghostSize.width * ghostSize.height, sizeof(UInt32)); CGContextRef ghostContext = CGBitmapContextCreate(ghostPixels,ghostSize.width, ghostSize.height,bitsPerComponent, ghostBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGContextDrawImage(ghostContext, CGRectMake(0, 0, ghostSize.width, ghostSize.height),ghostCGImage); /*现在已经到了把幽灵图像合并到你的照片中的最佳时间了。 合并:像前面提到的,每一个颜色都有一个透明通道来标识透明度。并且,你每创建一张图像,每一个像素都会有一个颜色值。 所以,如果遇到有透明度和半透明的颜色值该如何处理呢? 答案是,对透明度进行混合。在最顶层的颜色会使用一个公式与它后面的颜色进行混合。公式如下: NewColor = TopColor * TopColor.Alpha + BottomColor * (1 - TopColor.Alpha) ; 这是一个标准的线性差值方程。 ·当顶层透明度为1时,新的颜色值等于顶层颜色值。 ·当顶层透明度为0时,新的颜色值于底层颜色值。 ·最后,当顶层的透明度值是0到1之前的时候,新的颜色值会混合借于顶层和底层颜色值之间。 还可以用 premultiplied alpha的方法。 当处理成千上万像素的时候,他的性能会得以发挥。 */ //通过对幽灵图像像素数的循环和offsetPixelCountForInput获得输入的图像。记住,虽然你使用的是2维数据存储图像,但在内存他它实际上是一维的。 NSUInteger offsetPixelCountForInput = ghostOrigin.y * inputWidth + ghostOrigin.x; for (NSUInteger j = 0; j < ghostSize.height; j++) { for (NSUInteger i = 0; i < ghostSize.width; i++) { UInt32 * inputPixel = inputPixels + j * inputWidth + i + offsetPixelCountForInput; UInt32 inputColor = *inputPixel; UInt32 * ghostPixel = ghostPixels + j * (int)ghostSize.width + i; UInt32 ghostColor = *ghostPixel; // Do some processing here } } return inputImage; }
下一步,添加下面的代码到注释语句 Do some processing here的下面来进行混合:
// Blend the ghost with 50% alpha (重点) CGFloat ghostAlpha = 0.5f * (A(ghostColor) / 255.0); UInt32 newR = R(inputColor) * (1 - ghostAlpha) + R(ghostColor) * ghostAlpha; UInt32 newG = G(inputColor) * (1 - ghostAlpha) + G(ghostColor) * ghostAlpha; UInt32 newB = B(inputColor) * (1 - ghostAlpha) + B(ghostColor) * ghostAlpha; // Clamp, not really useful here :p newR = MAX(0,MIN(255, newR)); newG = MAX(0,MIN(255, newG)); newB = MAX(0,MIN(255, newB)); *inputPixel = RGBAMake(newR, newG, newB, A(inputColor));
这部分有2点需要说明:
1:你将幽灵图像的每一个像素的透明通道都乘以了0.5,使它成为半透明状态。然后将它混合到图像中像之前讨论的那样。
2:clamping部分将每个颜色的值范围进行限定到0到255之间,虽然一般情况下值不会越界。但是,大多数情况下需要进行这种限定防止发生意外的错误输出。
最后一句,将返回值替换
// Create a new UIImage CGImageRef newCGImage = CGBitmapContextCreateImage(context); UIImage * processedImage = [UIImage imageWithCGImage:newCGImage]; return processedImage;
黑白颜色
最后一种效果。尝试自己实现黑白颜色效果。为了做到这点,你需要把每一个像素的红色,绿色,蓝色通道的值设定成三个通道原始颜色值的平均值,就像开始的时候输出幽灵图像所有像素亮度值那样。
在注释语句// create a new UIImage前添加上一步的代码 。
// Convert the image to black and white for (NSUInteger j = 0; j < inputHeight; j++) { for (NSUInteger i = 0; i < inputWidth; i++) { UInt32 * currentPixel = inputPixels + (j * inputWidth) + i; UInt32 color = *currentPixel; // Average of RGB = greyscale UInt32 averageColor = (R(color) + G(color) + B(color)) / 3.0; *currentPixel = RGBAMake(averageColor, averageColor, averageColor, A(color)); } }
最后的一步就是清除内存。ARC不能代替你对CGImageRefs和CGContexts进行管理。添加如下代码到返回语句之前。
CGColorSpaceRelease(colorSpace); CGContextRelease(context); CGContextRelease(ghostContext); free(inputPixels); free(ghostPixels);
恭喜!你已经完成了自己的第一个图像处理程序。你可以在这里下载该工程的源代码。
raywenderlich.com/wp-content/uploads/2014/03/SpookCam-Starter.zip
还不错吧?你可以尝试修改一下循环中的代码创建自己想要的效果,尝试下实现下面的效果:
·尝试调换图像的红色和蓝色通道值
·提高图像的亮度10%
·作为进一步的挑战,尝试只使用基于像素的方法缩放幽灵的图像,下面是步骤:
1:使用幽灵图像的尺寸大小创建一个新的CGContext。
2:在原图像中得到你想要的并赋值到新的缓存图像中。
3:附加,尝试在像素之前进行计算并插入相似值像素点。如果你可以在四个像素间进行插入,你自己就已经实现 Bilinear scaling(双线性插值法)了
相关文章推荐
- iOS中图形图像处理第一部分:位图图像原图修改
- IOS中图形图像处理第一部分:位图图像原图修改
- IOS中图形图像处理第一部分:位图图像原图修改
- IOS中图形图像处理第一部分:位图图像原图修改
- IOS中图形图像处理第一部分:位图图像原图修改
- iOS 图形图像处理 一 :位图图像原图修改
- iOS中图形图像处理第一部分:位图图像原图修改
- iOS中图形图像处理第一部分:位图图像原图修改
- IOS中图形图像处理第一部分:位图图像原图修改
- IOS中图形图像处理第一部分:位图图像原图修改
- iOS中图形图像处理第一部分:位图图像原图修改
- iOS开发_修改系统cell的图像会变模糊的处理
- IOS图像处理(7)绘制位图
- iOS图像处理(7)绘制位图
- IOS: Quartz2D图像处理
- iOS 第十章 图像处理
- [IOS 图像处理]--相机的各种处理效果DLCImagePickerController
- iOS:UIImage详解&图像处理
- MFC图像处理-DIB位图之CDib类
- IOS 17个常用代码整理 -- 图像处理--邮箱验证