2016-10-25 10:33:54 haluoluo211 阅读数 2267
图像处理(以及机器视觉)在学校里是一个很大的研究方向,很多研究生、博士生都在导师的带领下从事着这方面的研究。另外,就工作而言,也确实有很多这方面的岗位和机会虚位以待。而且这种情势也越来越凸显。那么图像处理到底都研究哪些问题,今天我们就来谈一谈。图像处理的话题其实非常非常广,外延很深远,新的话题还在不断涌现。下面给出的12个大的方向,系我认为可以看成是基础性领域的部分,而且它们之间还互有交叉

1、图像的灰度调节
图像的灰度直方图、线性变换、非线性变换(包括对数变换、幂次变换、指数变换等)、灰度拉伸、灰度均衡、直方图规定化等等)。
例如,直方图规定化(代码请见http://blog.csdn.net/baimafujinji/article/details/41146381)


CLAHE(contrast limited adaptive histogram equalization)自适应的直方图均衡(效果图来自 http://www.cnblogs.com/Imageshop/archive/2013/04/07/3006334.html) 

2、图像的几何变换
图像的平移、图像的镜像、转置、缩放和旋转。这里面其实还包含了插值算法(这是某些几何变换所必须的),例如最邻近插值法、双线性插值法等等)
几何变换同时和图像的滤镜特效是紧密联系的,某些特效的实现本质上就是某种类型的几何变换。例如



3、图像的特效与滤镜
这方面的应用很多,你可以想想Photoshop里面的滤镜。
文献Combining Sketch and Tone for Pencil Drawing Production中给出的将自然图像变成手绘素描图的效果



例如浮雕效果


贴图太烦了,更多效果请见http://blog.csdn.net/baimafujinji/article/details/50500757
4、图像增强
内容包括图像的平滑(简单平均、中值滤波、高斯平滑等)和锐化(例如Laplace方法)等。


增强处理中的很多算法其实和图像复原中的降噪算法是重合的。现在保持边缘(或纹理结构)的平滑算法属于研究热点。像那些美颜相机里的嫩肤算法都是以此为基础的。比较常见的双边滤波(我给出的代码请见http://blog.csdn.net/baimafujinji/article/details/41598455)


基于全变分方法的TV去噪(http://blog.csdn.net/baimafujinji/article/details/42110831)、基于PM方程的非线性扩散去噪(http://blog.csdn.net/baimafujinji/article/details/42110831)等等。

5、图像复原
广义上来说——图像降噪,图像去雾,图像去模糊 都属于这个范畴
去噪实例是我用MagicHouse(http://blog.csdn.net/baimafujinji/article/details/50500757)实现的中值滤波处理椒盐噪声的效果。此外,一些基于非局部均值的降噪算法是当前研究的热点(例如BM3D、NLM等)



图像去模糊(图片取自我的《数字图像处理原理与实践(Matlab版)》)



去雾代码请见(http://blog.csdn.net/baimafujinji/article/details/30060161)或参考我的《数字图像处理原理与实践(Matlab版)》



6、图像的压缩与编码

想想BMP图像如何转换成JPG,JPG如何变成PNG?这些都属于图像压缩编码所要探讨的内容。

7、边缘检测与轮廓跟踪
边缘检测在图像处理中是一个“古老”的话题了,我就不具体给例子了。下面是一个轮廓跟踪的例子



8、图像分割
你可以认为轮廓跟踪也是实现图像分割的一种途径。
这是我在《数字图像处理原理与实践(Matlab版)》中给出的一个例子——用分水岭算法对马铃薯图像进行分割。



9、图像的形态学处理
这也属于一种非常古老的图像处理方式了。包括膨胀、腐蚀、细化、击中/击不中、开/闭运算等。但一些对颗粒状物体进行计数的应用中它仍然非常有效。



10、图像的频域变换(或称正交变换)
傅立叶、离散余弦、沃尔什-哈达玛变换、K-L(卡洛南-洛伊)变换(也称霍特林变换或PCA)、小波变换(小波变换还分很多种,例如Haar小波、Daubechies小波等等)

仅仅进行频域变换其实并没有多大意义,它往往要与具体应用相结合来发挥作用。例如进行图像压缩、嵌入数字水印、进行图像融合、进行图像降噪等等。
例如,利用PCA进行图像压缩的例子请见 http://blog.csdn.net/baimafujinji/article/details/50373143(源代码请见我的博文) 


在比如,利用小波融合对由聚焦失败导致的图像模糊进行修复 (本来左图和中图各有部分看不清,融合后变得可以辨识)源代码可见 http://blog.csdn.net/baimafujinji/article/details/49642111 


11、图像融合
广义上说融合至少包含三部分内容:像上面的基于小波的Fusion我们也认识是融合的一种,另外一种是以隐藏为目的类似嵌入式的融合,第三种是matting。matting有时反义成抠图,其实它最原本的意思就是融合。如果你理解
I = aF +(1-a)B这个融合公式的话,你应该明白我在所什么。这本质上和第二种融合原理是一样的。
狭义上,融合就是指matting。
例如 著名的Possion融合,下图右,如果直接把月亮图贴上天空,矩形边缘是很明显的,融合处理后的左图则很自然。

代码可见 http://blog.csdn.net/baimafujinji/article/details/46787837 

电影技术中常用matting方法来替换人物的场景。例如


12、图像信息安全
主要包括两个内容:1)数字水印(主要用于多媒体的版权保护);2)图像的加密(主要用于图像信息的保护)
例子是我用MagicHouse(http://blog.csdn.net/baimafujinji/article/details/50500757)实现的加密效果



注意上面我们所讨论的领域仅仅是图像处理的范畴,并不涉及机器视觉。所以也没有任何机器学习的内容,有时间我们再继续讨论这方面的东西。
2019-11-11 12:06:04 shenfuli 阅读数 397

AI垃圾分类

产品描述

如何进行垃圾分类已经成为居民生活的灵魂拷问,然而AI在垃圾分类的应用可以成为居民的得力助手。
针对目前业务需求,我们设计一款APP,来支撑我们的业务需求,主要提供文本,语音,图片分类功能。AI智能垃圾分类主要通过构建基于深度学习技术的图像分类模型,实现垃圾图片类别的精准识别重点处理图片分类问题。
采用深圳市垃圾分类标准,输出该物品属于可回收物、厨余垃圾、有害垃圾和其他垃圾分类。

垃圾分类-数据分析和预处理

  • 整体数据探测
  • 分析数据不同类别分布
  • 分析图片长宽比例分布
  • 切分数据集和验证集
  • 数据可视化展示(可视化工具 pyecharts,seaborn,matplotlib)

代码结构

├── data
│   ├── garbage-classify-for-pytorch
│   │   ├── train
│   │   ├── train.txt
│   │   ├── val
│   │   └── val.txt
│   └── garbage_label.txt
├── analyzer
│   ├── 01 垃圾分类_一级分类 数据分布.ipynb
│   ├── 02 垃圾分类_二级分类 数据分析.ipynb
│   ├── 03 数据加载以及可视化.ipynb
│   ├── 03 数据预处理-缩放&裁剪&标准化.ipynb
│   ├── garbage_label_40 标签生成.ipynb
├── models
│   ├── alexnet.py
│   ├── densenet.py
│   ├── inception.py
│   ├── resnet.py
│   ├── squeezenet.py
│   └── vgg.py
├── facebook
│   ├── app_resnext101_WSL.py
│   ├── facebookresearch_WSL-Images_resnext.ipynb
│   ├── ResNeXt101_pre_trained_model.ipynb
├── checkpoint
│   ├── checkpoint.pth.tar
│   ├── garbage_resnext101_model_9_9547_9588.pth
├── utils
│   ├── eval.py
│   ├── json_utils.py
│   ├── logger.py
│   ├── misc.py
│   └── utils.py
├── args.py
├── model.py
├── transform.py
├── garbage-classification-using-pytorch.py
├── app_garbage.py
  • data: 训练数据和验证数据、标签数据
  • checkpoint: 日志数据、模型文件、训练过程checkpoint中间数据
  • app_garbage.py:在线预测服务
  • garbage-classification-using-pytorch.py:训练模型
  • models:提供各种pre_trained_model ,例如:alexlet、densenet、resnet,resnext等
  • utils:提供各种工具类,例如;重新flask json 格式,日志工具类、效果评估
  • facebook: 提供facebook 分类器神奇的分类预测和数据预处理
  • analyzer: 数据分析和数据预处理模块
  • transform.py:通过pytorch 进行数据预处理
  • model.py: resnext101 模型集成以及调整、模型训练和验证函数封装

resnext101网络架构

  • pre_trained_model resnext101 网络架构原理
  • 基于pytorch 数据处理、resnext101 模型分类预测
  • 在线服务API 接口

垃圾分类-训练

python garbage-classification-using-pytorch.py \
        --model_name resnext101_32x16d \
        --lr 0.001 \
        --optimizer  adam \
        --start_epoch 1 \
        --epochs 10 \
        --num_classes 40
  • model_name 模型名称
  • lr 学习率
  • optimizer 优化器
  • start_epoch 训练过程断点重新训练
  • num_classes 分类个数

垃圾分类-评估

python garbage-classification-using-pytorch.py \
    --model_name resnext101_32x16d \
    --evaluate  \
    --resume checkpoint/checkpoint.pth.tar \
    --num_classes 40 
  • model_name 模型名称
  • evaluate 模型评估
  • resume 指定checkpoint 文件路径,保存模型以及训练过程参数

垃圾分类-在线预测

python app_garbage.py \
    --model_name resnext101_32x16d \
    --resume checkpoint/garbage_resnext101_model_2_1111_4211.pth
  • model_name 模型名称
  • resume 训练模型文件路径
  • 模型预测
    命令行验证和postman 方式验证
    举例说明:命令行模式下预测
    curl -X POST -F file=@cat.jpg http://ip:port/predict

最后,我们从0到1教大家掌握如何进行垃圾分类。通过本学习,让你彻底掌握AI图像分类技术在我们实际工作中的应用。

更多精彩内容,https://github.com/shenfuli/ai

具体视频讲解请关注:

PyTorch实战-深度学习之图像分类(垃圾分类案例)

https://edu.csdn.net/course/detail/26983

1. 你是什么垃圾?

在这里插入图片描述

2. 告诉你,你是什么垃圾

在这里插入图片描述

3. 使用它告诉你,你是啥垃圾在这里插入图片描述

4.走进垃圾世界,掌握垃圾处理

在这里插入图片描述

2016-04-10 15:23:59 qq_20823641 阅读数 12270

当我们学习c++ MFC的时候,总会看到界面,所以当在用Matlab的时候也想用GUI,这里首先写一点关于GUI界面的一些基础的操作,基本包括了不同的控件的使用,也会给很多的小例子,同时主要是对爱生活,爱网络,爱MATLAB~这个大神的视频的记录,不过真很好,所以就把过程写下来,让更多想入门GUI的有点帮助,当然这篇文章 主要的是基础的应用,后面也做出类似图像处理的GUI界面。整体代码工程在我的资源里面可以下载,下面也会给出分步代码创建可以用来理解、练习。http://download.csdn.net/detail/qq_20823641/9486571

本文主要从三个方面

  1.   打开GUI,初步了解它长什么样子
  2.   一步的步骤是什么?     画控件---属性修改---回调函数
  3.   其他的注意事项       属性中的max min和value是有关的,回调函数都是要获的一个东西,然后显示一个东西,当然如何传递,还要主要属性里面的Tag,string也是经常修改的,新建 文件 新建子菜单,怎么打开一个已经存在的文件的fig模式重新画控件等等

    为了不太长就绘制了一个总图,图像结果包括下面的所有的操作,希望看完以后大家可以自己动手去建立一下,很有意思,也会给图像处理GUI打下基础,相应的代码和文件会上传到csdn的资源,,找不到也可以QQ。

                     

          下面介绍的是具体的操作,注意在做的时候,一定要先看看上图中,你想要画的是什么,然后找控件,再定义回调函数。

         1 打开GUI

            直接输入guide或者选择打开Matlab  新建Gui File--New--GUI guide出现如下窗口 可点击Blank GUI,出现布局窗口,同时注意

openexisting GUI,如果你关闭了GUI页面可以从这个选型恢复,和运行.m出来是不一样的。

           随便画一个控件,保存文件会生成2个文件,一个是fig,里面包含了你设计的控件,还有一个是m文件,里面是所有空间的函数

双击刚才绘制的按钮,就可以对他的属性进行修改,例如stype就修改就会改变控件的类型从按钮可以编程text等,

有了空间就要给定义,实现他的功能,这里就要编写他的回调函数,对着控件右击看到view callbacks callback回调函数

        2 创建一个文本传递的GUI

       

            Guide,添加一个edittext , 双击修改属性 ,fontsize可以修改字体的大小,string显示的内容 ,tag会对应先后顺序,复制一个edit text 然后加入一个botton,右击它编写他的回调函数  

                           

<pre name="code" class="html">     function pushbutton1_Callback(hObject, eventdata, handles)
     str=get(handles.edit1,'String');%得到edit1中的字符
     set(handles.edit2,'String',str);%edit2中的字符得到str
<span style="text-align: center; background-color: rgb(240, 240, 240);">    </span><span style="font-size:18px; text-align: center; font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">图像结果在上面的图中。</span>

       3 例如设置GUI窗口大小可以调

        在GUI.fig图形设置中,在菜单栏选择tools-GUIoptions-resizebehavior 选择proportional

        4  例如滚动条数值显示在文本框中

        需要一个滚动条slider,双击看属性,里面有MAX MIN滑动范围 还有Tag value string,还有一个static text 双击属性string fontsize,再来一个edit text

       右击滚动条slider,定义回调函数   

 functionslider1_Callback(hObject, eventdata, handles)
 var=get(handles.slider1,'value');%得到滚动条中value
                                set(handles.edit3,'String',num2str(var));%编辑框得到数值,此处需要把数值型转化成字符型

        5 例如radio button check box和toggle button的基本用法

         添加一个radio button 双击属性 max min 选中按钮 显示最大值 取消选中显示最小值 value  tag(radiobutton1改成kj1), string删除不显示注意如果你的max 10 min -1 而value还是0就会出错,因为显示的要么是最小要么是做大,所以要改变value为-1,添加一个edit text 其中Tag改成kj1,右击radio button进行回调函数定义

                                      

                   functionkj1_Callback(hObject, eventdata, handles)
   var=get(handles.kj1,'value');
                 set(handles.kj2,'string',num2str(var))

          添加一个 check box 双击查看属性 max min 和value要对应,tag中改成kj3,添加一个edit text 修改 string tag为kj4

         编辑check box的回调函数                                  

                  functionkj3_Callback(hObject, eventdata, handles)
var=get(handles.kj3,'value');
           set(handles.kj4,'string',num2str(var))

            最后添加一个toggle button,max min value,tag改成kj5,添加一个edit,其中tag改成kj6

            编辑toggle button的回调函数

 functionkj5_Callback(hObject, eventdata, handles)
var=get(handles.kj5,'value');
set(handles.kj6,'string',num2str(var));

       6 例如button group 绘制不同的正弦曲线

          首先创建一个button group控件,双击属性,title修改成绘制不同正弦曲线,然后创建3个radio button 在group中,第一个string改成sin(x) tag改成kj7,第二个string改成cos (x) tag改成kj8, 第三个string改成sin(x)+cos(x )tag改成kj9,注在group中的按钮只能有一个被选中,添加一个坐标轴按钮axes,右击button group进行回调函数,此处view 然后callbacks------selectionchangefcn

           

  functionuipanel1_SelectionChangeFcn(hObject, eventdata, handles)%选择发生变换
   x=0:0.01:2*pi;
  current_Obj=get(eventdata.NewValue,'Tag');%当前选择的控件,结构体,当选被选择的控件的Tag,得到当前被选中的radio button的tag,赋值给current_Obj
     %绘制前要选择当前的坐标系,查看tag
   axes(handles.axes1)
  switchcurrent_Obj%判断哪一个radio button被选中
             case 'kj7'
             y=sin(x);
           plot(x,y);
           case 'kj8'
          y=cos(x);
         plot(x,y);
         case 'kj9'
        y=sin(x)+cos(x);
        plot(x,y);
         end

    7  例如下拉菜单pop up menu的基本用法

        首先创建一个 pop up按钮,双击查看属性 string 输入三个函数 ,tag改成kj10,此处的value对应可以下1或者2或者3,对应显示的string的三个函数中的其中一个

创建一个axes坐标系控件, 定义回调函数

                       

% --- Executes on selection change in kj10.
function kj10_Callback(hObject, eventdata, handles)
% hObject    handle to kj10 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: contents = cellstr(get(hObject,'String')) returns kj10 contents as cell array
%        contents{get(hObject,'Value')} returns selected item from kj10
var=get(handles.kj10,'value');%得到下拉菜单空间的value值,以便确定点击的是控件的哪一行
x=0:0.01:2*pi;
axes(handles.axes2)
switch var
    case 1
        y=sin(x);
        plot(x,y);
    case 2
        y=cos(x);
        plot(x,y);
    case 3
        y=sin(x)+cos(x);
        plot(x,y);
end


         

     8 例如listbox的基本用法

  

     创建一个listbox控件,双击属性string添加四行 1 sin(x) cos(x) sin(x)+cos(x),其中value的数值对应string中的行

     创建一个push button控件,string中修改为选择x轴参数,同样建立一个选择y轴参数的控件,再分别对应2个edit控件

     首先对push button  X控件进行定义回调函数

                       functionpushbutton2_Callback(hObject, eventdata, handles)

selected_index=get(handles.listbox1,'value');

str=get(handles.listbox1,'string');

set(handles.edit7,'string',str{selected_index});

       首先对push button  X控件进行定义回调函数

                        functionpushbutton2_Callback(hObject, eventdata, handles)

selected_index=get(handles.listbox1,'value');

str=get(handles.listbox1,'string');

set(handles.edit8,'string',str{selected_index});

       然后仅需listbox的view callbacks creatfcn

functionlistbox1_CreateFcn(hObject, eventdata, handles)

。。。。。。。。

添加

t=0:0.01:2*pi;

canshu1=t;

   canshu2=sin(t);

canshu3=cos(t);

canshu4=sin(t)+cos(t);

                                   CS={canshu1,canshu2,canshu3,canshu4};

handles.CS=CS;

                                                      guidata(hObject,handles)%保存和更新handles结构体再在选择X参数控件右击定义回调函数

        分别对选择X参数和选择Y参数2个空间进行回调函数添加下面三行代码

x=handles.CS{selected_index};

handles.x=x;

guidata(hObject,handles);

、、、、、、

y=handles.CS{selected_index};

handles.y=y;

guidata(hObject,handles);

          添加一个绘图曲线的push button

        创建一个坐标系axes

        对绘制曲线的控件进行回调函数

                                                         functionpushbutton4_Callback(hObject, eventdata, handles)

axes(handles.axes3)

plot(handles.x,handles.y)

axisequal

     9 例如 菜单控件menu的基本用法

     

点击menu editor -menu bar(菜单栏)(context menus是右击显示的菜单)

新建一个new menu   label改成绘图

在new menu下新建2个菜单(注点击图像菜单上左边第一个图标是新建菜单,左边第二个是新建子菜单),把2个子菜单lable tag分别改成正弦曲线sinx  余弦曲线 cosx

然后创建一个坐标系控件axes,然后再次进入menu editor,对正弦曲线进行回调函数编辑,此处先点击子菜单正弦曲线,然后点击view进去编辑

functionsinx_Callback(hObject, eventdata, handles)

x=0:0.01:2*pi;

y=sin(x);

axes(handles.axes4)

h=plot(x,y);

handles.h=h;

guidata(hObject,handles)

同样的方法进行余弦,区别就是在回调函数,y=sin(x)变成了y=cos(x)

下面来定义一个弹出菜单,就是右击的时候显示的菜单

首先要选择menu editor下的context menus,然后新建一个菜单(左边第三个图标),lable改成line,然后再建立line子菜单,lable改成颜色,然后再建立颜色的子菜单三个,lable tag 分别改成红色 red  绿色green 黑色 black

再建立一个line的子菜单,lable改成线宽,tag改成xiankuan,然后线宽下再建立三个子菜单,lable tag 分别改成线宽1 xiankuan_1 线宽2 xiankuan_2 线宽3 xiankuan_3

想要右击出来菜单还要做一步

双击坐标轴4,找到一个属性叫做UIContextMenu,下拉选择我们定义的Line

这样就可以右击显示菜单了,但是功能不能使用,还要做

点击menu editor 点击contex menus分别进行点击view然后定义回调函数,就是分别在自己的回调函数内添加相应的函数,一个是一行

set(handles.h,'color','r');%红色

set(handles.h,'color','g');%绿色

set(handles.h,'color','k');%黑色

同样的方法进行线宽的回调函数

set(handles.h,'LineWidth',1);

set(handles.h,'LineWidth',2);

set(handles.h,'LineWidth',3);

2014-11-01 10:09:43 u012681458 阅读数 664

iOS中图形图像处理第一部分:位图图像原图修改


 
想象一张最好的生活自拍照。它是很高大尚滴并且以后会有用武之地。转发,票选将会使你获得成千上万份的关注,因为它确实很酷很帅。现在,如果你有什么办法,可以让它看起来更加的高大尚。。。
 
这就是图形图像处理要做到的!它可以让你的照片带上更多的特殊效果,比如修改颜色,与其它的图片进行合成等等。
 
在这两部分教程中,你需要先弄明白一些图形图像处理的基础知识。接着,你可以利用如下四个流行的图形图像处理方法编写一个实现“幽灵图像过滤器”的程序:
1:位图图像原图修改
2:使用Core Graphics库
3:使用Core Image库
4:使用GPUImage库的第三部分
 
在图形图像处理教程的第一节,主要讲解位图图像原图的修改。一但你明白基本的图形处理方法,那么其它的相关内容你也会较容易的弄明白。在教程的第二部分,主要介绍另外的三种修改图像方法。
 
本教程假设你拥有关于IOS系统和Object-C的基础,但在开始本教程前不需要拥有任何关于图形图像处理的知识。
 
开始
在开始写代码之前,先理解一些关于图形图像处理的基本概念很是需要。所以,先别急,放轻松,让我们在最短的时间里去了解一下图形图像的内部工作原理。
 
第一件事情,看一下我们本教程中的新朋友...幽灵!
 
 
不要怕,幽灵不是真的鬼魂。实际上,它只是一张图像。简单来说,它就是由一堆1和0组成的。这样说听上去会更好一些。
 
什么是图形图像
一张图像就是像素点的集合,每一个像素都是一个单独,明了的颜色。图像一般情况下都存储成数组,你可以把他们相像成2维数组。
 
这一张是缩放版本的幽灵,被放大后:
 
 
图像中这些小的“方块”就是像素,每一像素只表示一种颜色。当成百上千万的像素集体到一起后,就构成了图形图像。
 
如何用字节来表示颜色
表示图形的方式有许多种。在本教程中使用的是最简单的:32位RGBA模式。
 
如同它的名字一样,32位RGBA模式会将一个颜色值存储在32位,或者4个字节中。每一个字节存储一个部分或者一个颜色通道。这4个部分分别是:
 
~ R代表红色
 
~ G代表绿色
 
~ B代表蓝色
 
~ A代表透明度
 
正如你所知道的,红,绿和蓝是所有颜色的基本颜色集。你几乎可以使用他们创建搭配出任何想要的颜色。
 
由于使用8位表示每一种颜色值,那么使用32位RGBA模式实际上可以创建出不透明的颜色的总数是256256256种,已经接近17亿种。惊叹,那是好多好多好多的颜色!
 
alpha通道与其它的不同。你可以把它当成透明的东西,就像UIView的alpah属性。
 
透明颜色意味着没有任何的颜色,除非在它的后面有另外一种颜色;它的主要功能就是要告诉图像处理这个像素的透明度是多少,于是,就会有多少颜色值穿透过它而显示出来。
 
你将会通过本节后面的内容更新深入的了解。
 
总结一下,一个图形就是像素的集体,并且每一个像素只能表示一种颜色。本节,你已经了解了32位RGBA模式。
 
提示:你有没有想过,位图的结构组成?一张位图就是一张2D的地图,每一块就是一个像素!像素就是地图的每一块。哈哈!
 
现在你已经了解了用字节表示颜色的基础了。不过在你开始着手写代码前,还有三个以上的概念需要你了解。
 
颜色空间
使用RGB模式表示颜色是颜色空间的一个例子。它只是众多存储颜色方法中的一种。另外一种颜色空间是灰阶空间。像它的名字一样,所有的图形都只有黑和白,只需要保存一个值来表示这种颜色。
 
下面这种使用RGB模式表示的颜色,人类的肉眼是很难识别的。
Red: 0 Green:104 Blue:55
 
你认为RGB值为[0,104,55]会产生一种什么颜色?
 
认真的思考一下,你也许会说是一种蓝绿色或者绿色,但那是错的。原来,你所看到的是深绿色。
 
另外两种比较常见的颜色空间是HSV和YUV。
 
HSV,使用色调,饱和度和亮度来直观的存储颜色值。你可以把这三个部分这样来看:
 
·色调就是颜色
·饱和度就是这个颜色有多么的饱满
·值就是颜色的亮度有多亮
 
在这种颜色空间中,如果你发现自己并不知道HSV的值,那么通过它的三个值,可以很容易的相像出大概是什么颜色。
 
RGB和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在绘制图像的时候,会使用大量内存把图像的原始尺寸进行解压缩。如果你的程序占用了过多的内存,那么操作系统会将进程杀死(程序崩溃)。所以请确定你的程序使用较大的图像进行过测试。
 
我需要一些行动…
 
关注一下像素
现在,你已经基础了解了图形图像的内部工作原理,已经可以开始编写代码喽。今天你将会开发一款改变自己照片的程序,叫做SpookCam,该程序会把一张幽灵的图像放到你的照片中!
 
下载工具包在xcode中打开该项目,编译并运行。在你的手机上会看到如下的图像:
 
 
在控制台,你会看到如下的输出:
 
 
 
当前的程序可以加载这张幽灵的图像,并得到图像的所有像素值,打印出每个像素的亮度值到日志中。
 
亮度值是神马?它就是红色,绿色和蓝色通过的平均值。
 
注意输出日志外围的亮度值都为0,这意味着他们代码的是黑色。然而,他们的透明度的值是0,所以它们是透明不可见的。为了证明这一点,试着将imageView的背景颜色设置成红色,然后再次编译并运行。
 
 
 
现在快速的浏览一下代码。ViewController.m 中使用 UIImagePickerController 来在相册中取得图像或者使用机机获得图像。
 
当它选定一张图像后,调用-setupWithImage:在这行中,输出了每一像素的亮度值到日志中。定位到ViewController.m中的logPixelsOfImage,查看方法中的开始部分:
 
  1. // 1. 
  2. CGImageRef inputCGImage = [image CGImage]; 
  3. NSUInteger width =                 CGImageGetWidth(inputCGImage); 
  4. NSUInteger height = CGImageGetHeight(inputCGImage); 
  5.  
  6. // 2. 
  7. NSUInteger bytesPerPixel = 4; 
  8. NSUInteger bytesPerRow = bytesPerPixel *     width; 
  9. NSUInteger bitsPerComponent = 8; 
  10.  
  11. UInt32 * pixels; 
  12. pixels = (UInt32 *) calloc(height * width,     sizeof(UInt32)); 
  13.  
  14. // 3. 
  15. CGColorSpaceRef colorSpace =     CGColorSpaceCreateDeviceRGB(); 
  16. CGContextRef context =     CGBitmapContextCreate(pixels, width, height,     bitsPerComponent, bytesPerRow, colorSpace,     kCGImageAlphaPremultipliedLast |     kCGBitmapByteOrder32Big); 
  17.  
  18. // 4. 
  19. CGContextDrawImage(context, CGRectMake(0,     0, width, height), inputCGImage); 
  20.  
  21. // 5. Cleanup 
  22. CGColorSpaceRelease(colorSpace); 
  23. CGContextRelease(context); 
 
现在,让我们分段的来看一下:
 
1:第一部分:把UIImage对象转换为需要被核心图形库调用的CGImage对象。同时,得到图形的宽度和高度。
 
2:第二部分:由于你使用的是32位RGB颜色空间模式,你需要定义一些参数bytesPerPixel(每像素大小)和bitsPerComponent(每个颜色通道大小),然后计算图像bytesPerRow(每行有大)。最后,使用一个数组来存储像素的值。
 
3:第三部分:创建一个RGB模式的颜色空间CGColorSpace和一个容器CGBitmapContext,将像素指针参数传递到容器中缓存进行存储。在后面的章节中将会进一步研究核图形库。
 
4:第四部分:把缓存中的图形绘制到显示器上。像素的填充格式是由你在创建context的时候进行指定的。
 
5:第五部分:清除colorSpace和context.
 
NOTE:当你绘制图像的时候,设备的GPU会进行解码并将它显示在屏幕。为了访问本地数据,你需要一份像素的复制,就像刚才做的那样。
 
此时此刻,pixels存储着图像的所有像素信息。下面的几行代码会对pixels进行遍历,并打印:
  1. // 1. 
  2. #define Mask8(x) ( (x) & 0xFF ) 
  3. #define R(x) ( Mask8(x) ) 
  4. #define G(x) ( Mask8(x >> 8 ) ) 
  5. #define B(x) ( Mask8(x >> 16) ) 
  6.  
  7. NSLog(@"Brightness of image:"); 
  8. // 2. 
  9. UInt32 * currentPixel = pixels; 
  10. for (NSUInteger j = 0; j < height; j++) { 
  11.   for (NSUInteger i = 0; i < width; i++) { 
  12.     // 3. 
  13.     UInt32 color = *currentPixel; 
  14.     printf("%3.0f ",     (R(color)+G(color)+B(color))/3.0); 
  15.     // 4. 
  16.     currentPixel++; 
  17.   } 
  18.   printf("\n"); 
 
代码解释:
 
1:定义了一些简单处理32位像素的宏。为了得到红色通道的值,你需要得到前8位。为了得到其它的颜色通道值,你需要进行位移并取截取。
 
2:定义一个指向第一个像素的指针,并使用2个for循环来遍历像素。其实也可以使用一个for循环从0遍历到width*height,但是这样写更容易理解图形是二维的。
 
3:得到当前像素的值赋值给currentPixel并把它的亮度值打印出来。
 
4:增加currentPixel的值,使它指向下一个像素。如果你对指针的运算比较生疏,记住这个:currentPixel是一个指向UInt32的变量,当你把它加1后,它就会向前移动4字节(32位),然后指向了下一个像素的值。
 
提示:还有一种非正统的方法就是把currentPiexl声明为一个指向8字节的类型的指针,比如char。这种方法,你每增加1,你将会移动图形的下一个颜色通道。与它进行位移运算,你会得到颜色通道的8位数值。
 
此时此刻,这个程序只是打印出了原图的像素信息,但并没有进行任何修改!下面将会教你如何进行修改。
 
SpookCame-原图修改
四种研究方法都会在本小节进行,你将会花费更多的时间在本节,因为它包括了图形图像处理的第一原则。掌握了这个方法你会明白其它库所做的。
 
在本方法中,你会遍历每一个像素,就像之前做的那个,但这次,将会对每个像素进行新的赋值。
 
这种方法的优点是容易实现和理解;缺点就是扫描大的图形和效果的时候会更复杂,不精简。
 
正如你在程序开始看到的,ImageProcessor类已经存在。将它应用到ViewController中,替换-setupWithImage,代码如下:
 
  1. - (void)setupWithImage:(UIImage*)image { 
  2.   UIImage * fixedImage = [image     imageWithFixedOrientation]; 
  3.   self.workingImage = fixedImage; 
  4.  
  5.   // Commence with processing! 
  6.   [ImageProcessor     sharedProcessor].delegate = self; 
  7.   [[ImageProcessor sharedProcessor]     processImage:fixedImage]; 
 
注释掉 -viewDidLoad 中下面的代码:
  1. // [self setupWithImage:[UIImage     imageNamed:@"ghost_tiny.png"]]; 
现在,打开 ImageProcessor.m。如你所见,ImageProcessor 是单例模式,调用 -processUsingPixels 来加载图像,然后通过 ImageProcessorDelegate 返回输出。
 
-processsUsingPixels:是之前你所看到获得图形像素代码的一种复制品,如同inputImage。注意两个额外的宏A(x)和RGBAMake(r,g,b,a)的定义,用来方便处理。
 
编译,并运行。从相册(拍照)选择一张图片,它将会出现在屏幕上:
 
 
 
照片中的人看上去在放松,是时候把幽灵放进去了!
 
在processUsingPixels的返回语句前,添加如下代码,创建一个幽灵的CGImageRef对象。
UIImage * ghostImage = [UIImage imageNamed:@"ghost"];
CGImageRef ghostCGImage = [ghostImage CGImage];
 
现在,做一些数学运算来确定幽灵图像放在原图的什么位置。
 
  1. CGFloat ghostImageAspectRatio =     ghostImage.size.width /     ghostImage.size.height; 
  2. NSInteger targetGhostWidth = inputWidth *     0.25; 
  3. CGSize ghostSize =     CGSizeMake(targetGhostWidth, targetGhostWidth     / ghostImageAspectRatio); 
  4. CGPoint ghostOrigin =     CGPointMake(inputWidth * 0.5, inputHeight *     0.2); 
 
以上代码会把幽灵的图像宽度缩小25%,并把它的原点设定在点ghostOrigin。
 
下一步是创建一张幽灵图像的缓存图,
 
  1. NSUInteger ghostBytesPerRow = bytesPerPixel * ghostSize.width; 
  2. UInt32 * ghostPixels = (UInt32     *)calloc(ghostSize.width * ghostSize.height,     sizeof(UInt32)); 
  3.   
  4. CGContextRef ghostContext =     CGBitmapContextCreate(ghostPixels,     ghostSize.width, ghostSize.height, 
  5.                                        bit    sPerComponent, ghostBytesPerRow, colorSpace, 
  6.                                        kCG    ImageAlphaPremultipliedLast |     kCGBitmapByteOrder32Big); 
  7. CGContextDrawImage(ghostContext,     CGRectMake(0, 0, ghostSize.width,     ghostSize.height),ghostCGImage); 
 
上面的代码和你从inputImage中获得像素信息一样。不同的地方是,图像会被缩小尺寸,变得更小了。
 
现在已经到了把幽灵图像合并到你的照片中的最佳时间了。
 
合并:像前面提到的,每一个颜色都有一个透明通道来标识透明度。并且,你每创建一张图像,每一个像素都会有一个颜色值。
 
所以,如果遇到有透明度和半透明的颜色值该如何处理呢?
 
答案是,对透明度进行混合。在最顶层的颜色会使用一个公式与它后面的颜色进行混合。公式如下:
 
  1. NewColor = TopColor * TopColor.Alpha + BottomColor * (1 - TopColor.Alpha) 
 
这是一个标准的线性差值方程。
 
·当顶层透明度为1时,新的颜色值等于顶层颜色值。
·当顶层透明度为0时,新的颜色值于底层颜色值。
·最后,当顶层的透明度值是0到1之前的时候,新的颜色值会混合借于顶层和底层颜色值之间。
 
还可以用 premultiplied alpha的方法。
 
当处理成千上万像素的时候,他的性能会得以发挥。
 
好,回到幽灵图。
 
如同其它位图运算一样,你需要一些循环来遍历每一个像素。但是,你只需要遍历那些你需要修改的像素。
 
把下面的代码添加到processUsingPixels的下面,还是放在返回语句的前面:
 
  1. NSUInteger offsetPixelCountForInput = ghostOrigin.y * inputWidth + ghostOrigin.x; 
  2. for (NSUInteger j = 0; j < ghostSize.height; j++) { 
  3. for (NSUInteger i = 0; i < ghostSize.width; i++) { 
  4. UInt32 * inputPixel = inputPixels + j * inputWidth + i + offsetPixelCountForInput; 
  5. UInt32 inputColor = *inputPixel; 
  6.   
  7.     UInt32 * ghostPixel = ghostPixels + j     * (int)ghostSize.width + i; 
  8.     UInt32 ghostColor = *ghostPixel; 
  9.   
  10.     // Do some processing here       
  11.   } 
 
通过对幽灵图像像素数的循环和offsetPixelCountForInput获得输入的图像。记住,虽然你使用的是2维数据存储图像,但在内存他它实际上是一维的。
 
下一步,添加下面的代码到注释语句 Do some processing here的下面来进行混合:
 
  1. // Blend the ghost with 50% alpha 
  2. CGFloat ghostAlpha = 0.5f * (A(ghostColor)     / 255.0); 
  3. UInt32 newR = R(inputColor) * (1 -     ghostAlpha) + R(ghostColor) * ghostAlpha; 
  4. UInt32 newG = G(inputColor) * (1 -     ghostAlpha) + G(ghostColor) * ghostAlpha; 
  5. UInt32 newB = B(inputColor) * (1 -     ghostAlpha) + B(ghostColor) * ghostAlpha; 
  6.   
  7. // Clamp, not really useful here :p 
  8. newR = MAX(0,MIN(255, newR)); 
  9. newG = MAX(0,MIN(255, newG)); 
  10. newB = MAX(0,MIN(255, newB)); 
  11.   
  12. *inputPixel = RGBAMake(newR, newG, newB,     A(inputColor)); 
 
这部分有2点需要说明:
 
1:你将幽灵图像的每一个像素的透明通道都乘以了0.5,使它成为半透明状态。然后将它混合到图像中像之前讨论的那样。
 
2:clamping部分将每个颜色的值范围进行限定到0到255之间,虽然一般情况下值不会越界。但是,大多数情况下需要进行这种限定防止发生意外的错误输出。
 
最后一步,添加下面的代码到 processUsingPixels 的下面,替换之前的返回语句:
  1. // Create a new UIImage 
  2. CGImageRef newCGImage =     CGBitmapContextCreateImage(context); 
  3. UIImage * processedImage = [UIImage     imageWithCGImage:newCGImage]; 
  4.   
  5. return processedImage; 
上面的代码创建了一张新的UIImage并返回它。暂时忽视掉内存泄露问题。编译并运行,你将会看到漂浮的幽灵图像:
 
 
好了,完成了,这个程序简直就像个病毒!
 
黑白颜色
最后一种效果。尝试自己实现黑白颜色效果。为了做到这点,你需要把每一个像素的红色,绿色,蓝色通道的值设定成三个通道原始颜色值的平均值,就像开始的时候输出幽灵图像所有像素亮度值那样。
 
在注释语句// create a new UIImage前添加上一步的代码 。
 
找到了吗?
 
  1. // Convert the image to black and white 
  2. for (NSUInteger j = 0; j < inputHeight; j++) { 
  3. for (NSUInteger i = 0; i < inputWidth; i++) { 
  4. UInt32 * currentPixel = inputPixels + (j * inputWidth) + i; 
  5. UInt32 color = *currentPixel; 
  6.   
  7.     // Average of RGB = greyscale 
  8.     UInt32 averageColor = (R(color) +     G(color) + B(color)) / 3.0; 
  9.   
  10.     *currentPixel = RGBAMake(averageColor,     averageColor, averageColor, A(color)); 
  11.   } 
 
最后的一步就是清除内存。ARC不能代替你对CGImageRefs和CGContexts进行管理。添加如下代码到返回语句之前。
  1. CGColorSpaceRelease(colorSpace); 
  2. CGContextRelease(context); 
  3. CGContextRelease(ghostContext); 
  4. free(inputPixels); 
  5. free(ghostPixels); 
编译并运行,不要被结果吓到:
 
 
下面需要做的:
 
恭喜!你已经完成了自己的第一个图像处理程序。你可以在这里下载该工程的源代码。
 
还不错吧?你可以尝试修改一下循环中的代码创建自己想要的效果,尝试下实现下面的效果:
 
·尝试调换图像的红色和蓝色通道值
·提高图像的亮度10%
·作为进一步的挑战,尝试只使用基于像素的方法缩放幽灵的图像,下面是步骤:
 
1:使用幽灵图像的尺寸大小创建一个新的CGContext。
2:在原图像中得到你想要的并赋值到新的缓存图像中。
3:附加,尝试在像素之前进行计算并插入相似值像素点。如果你可以在四个像素间进行插入,你自己就已经实现 Bilinear scaling(双线性插值法)了!太牛了!
 
如果你已经完成了第一个项目,想必你对图形图像的处理已经有了基本的概念。现在你可以尝试使用更快更好的方法来实现相同的效果。
 
在下一章节中,你将会使用另外三个新的方法替换-processUsingPixels:完成相同的任务。一定要看丫!
 
同时,如果你对该章节有任何疑问和不解,请留言给我!
2012-01-26 14:13:40 wl_soft50 阅读数 564

            从去年开始接触OpenCV用于视觉方向的开发,累积了不少关于图像处理方面的经验,也给自己生活增添了不少乐趣,为项目,为自己付出了不少努力。而今年项目需求,OpenCV已经远远不适于更深入的研究,虽然它已经有单目和双目视觉的各种给力的图像处理函数。基于OpenCV实现的不少小项目,但多多少少都有自己的缺陷,简单的例子就是利用OpenCV实现手势识别,利用肤色的话就会受类肤色块的影响,用轮廓会受到背景的影响,总之方法可以有很多种,但没有一种是完美的。自己也不断的研读别人关于背景去除的各种paper,结果都不尽理想。或许是自己研究的还是不够深入,还没有想到一个自己的算法去实现。

           项目是实现了最终的要的效果,但是限制性比较多。在去年的项目基础之上,又规划出今年的部分。我做了一个比较大的决定,就是利用OpenNI结合OpenCV去实现。之前学习OpenCV没有记录自己的学习过程,到最后别人问我关于OpenCV的问题时很多细节部分都遗忘掉了,导致各种坑爹。故写下随笔一篇,以提醒自己学习总结。

SVC案例

阅读数 568

没有更多推荐了,返回首页