用Matlab从图片中取出曲线的方法
利用 MATLAB 提取图片曲线
给你一张图片,如何提取里面曲线的数据,从而利用这些数据进行图像重绘、加工处理、测距、拟合得到函数表达式等操作呢?
行文动机
前段时间,有个朋友问了我一个问题,大概意思就是要给图像的流线测距离,在我的印象里面,MATLAB 是似乎没有这种直接的功能的。
那么换个角度来理解一下这个问题,如果给你一张图像,如何提取里面点的数据?其实,有了曲线的数据,后面想干嘛就干嘛了。
一直没空弄这个,今天偷闲,安排!!!
图像的读入与裁剪
以下面的图像作为例子。
我们先导入图像,进行简单的裁剪。为什么要裁剪呢?其实不裁剪也没关系,因为我后面是基于像素点的颜色来提取的曲线。如果你想提取的曲线不能通过颜色区分,那么,最好通过裁剪,把你不想要的部分尽可能地剪掉。
%% 读入图片,展示,有必要的话可以适当做一些裁剪 A = imread('a.jpg');%读取到一张图片 imshow(A); A = imcrop(A);%使用鼠标裁剪一波 imshow(A); [low_num,col_num,~] = size(A);
颜色拾取
观察图像发现,我们要提取的曲线是蓝色的,所以我希望通过颜色把它区分出来。那么我们就要知道这个曲线的 RGB 值。我希望通过鼠标点选的方式获取到颜色值。
这里我偷个懒,直接采用了 slandarer 开源的颜色提取工具。这个模块不是我写的,特此声明,请尊重原创。
%% 颜色提取 getcolor(); color = color_list_temp(1,:);
function getcolor global control; global ima; global GUI; global x_limit; global y_limit; global color; global a; global color_number; global color_list; global page; global total_page; global color_list_temp; color=[]; page=1; total_page=2; color_list=[0 0 0]; color_list(1,:)=[]; color_number=1; rgb_type=1; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GUI.fig=figure('units','pixels',... 'position',[350 100 800 500],... 'Numbertitle','off',... 'menubar','none',... 'resize','off',... 'name','getcolor',... 'color',[0.95 0.95 0.95]); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% uh1=uimenu('label','设置'); uimenu(uh1,'label','RGB类型设置','callback',@RGBset) function RGBset(~,~) GUI.rgbfig=figure('units','pixels',... 'position',[360 370 180 200],... 'Numbertitle','off',... 'menubar','none',... 'name','RGBset',... 'resize','off'); GUI.axes=axes('Units','pixels',... 'parent',GUI.rgbfig,... 'PlotBoxAspectRatio',[1 1 1],... 'Color',[0.95 0.95 0.95],... 'Box','on', ... 'XLim',[0 500],... 'YLim',[0 500], ... 'XColor',[0.95 0.95 0.95],... 'YColor',[0.95 0.95 0.95],... 'YDir','reverse', ... 'xtick',[],'ytick',[]); GUI.checkbox1=uicontrol('parent',GUI.rgbfig,... 'style','checkbox',... 'string','范围:0-1',... 'position',[45 150 400 30],... 'fontsize',10,... 'value',rgb_type,... 'callback',@ifon1); GUI.checkbox255=uicontrol('parent',GUI.rgbfig,... 'style','checkbox',... 'string','范围:0-255',... 'position',[45 120 400 30],... 'fontsize',10,... 'value',~rgb_type,... 'callback',@ifon255); GUI.makesurebutton=uicontrol('parent',GUI.rgbfig,... 'style','pushbutton',... 'string','确定设置',... 'position',[45 70 100 25],... 'fontsize',10,... 'callback',@settype); function ifon1(~,~) if(get(GUI.checkbox1,'value')==1) set(GUI.checkbox1,'value',1); set(GUI.checkbox255,'value',0); else set(GUI.checkbox1,'value',1); end end function ifon255(~,~) if(get(GUI.checkbox255,'value')==1) set(GUI.checkbox255,'value',1); set(GUI.checkbox1,'value',0); else set(GUI.checkbox255,'value',1); end end function settype(~,~) rgb_type=get(GUI.checkbox1,'value'); if ~isempty(color) set(GUI.text2,'string',['[',num2str((color/255).*rgb_type+color.*(~rgb_type)),']']); end show_color(page); close(GUI.rgbfig) end end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% uh2=uimenu('label','保存'); uimenu(uh2,'label','储存为mat','callback',@saveas_mat) uimenu(uh2,'label','储存为txt','callback',@saveas_txt) uimenu(uh2,'label','储存为excel','callback',@saveas_exl) uimenupic=uimenu(uh2,'label','储存为对照图'); uimenu(uimenupic,'label','储存全部页码','callback',@saveas_pic_all); uimenu(uimenupic,'label','储存当前页码','callback',@saveas_pic_now); function saveas_mat(~,~) try [filename, pathname] = uiputfile({'*.mat','mat'}); color_list_temp=(color_list/255).*rgb_type+color_list.*(~rgb_type); save([pathname,filename],'color_list_temp'); catch end end function saveas_txt(~,~) try [filename, pathname] = uiputfile({'*.txt','记事本'}); color_list_temp=(color_list/255).*rgb_type+color_list.*(~rgb_type); [m,n]=size(color_list_temp); fid=fopen([ pathname,filename],'w'); for ii=1:m for jj=1:n if jj==n fprintf(fid,'%d\r\n',color_list_temp(ii,jj)); else fprintf(fid,'%d\r\t',color_list_temp(ii,jj)); end end end fclose(fid); catch end end function saveas_exl(~,~) [filename, pathname] = uiputfile({'*.xlsx','记事本'}); color_list_temp=(color_list/255).*rgb_type+color_list.*(~rgb_type); xlswrite([ pathname,filename],color_list_temp) end function saveas_pic_all(~,~) page_with_color=total_page-1; px=50; gap_px=10; pic=ones(9*px,page_with_color*px+(page_with_color-1)*gap_px,3); for p=1:page_with_color for ii=(p-1)*9+1:p*9 for kk=1:3 if ii<=length(color_list) pic((ii-(p-1)*9-1)*px+1:(ii-(p-1)*9)*px,(p-1)*(px+gap_px)+1:(p-1)*(px+gap_px)+px,kk)=color_list(ii,kk)/255; end end end end [filename, pathname] = uiputfile({'*.jpg;*.png','All Image Files';... '*.jpg','JPG';'*.png','PNG' }); imwrite(pic,[pathname,filename]); end function saveas_pic_now(~,~) try [m,~]=size(color_list); m=m-(page-1)*9; m(m>9)=9; px=50; pic=ones(9*px,1*px,3); if m>0 for ii=(page-1)*9+1:(page-1)*9+m for kk=1:3 pic((ii-1)*px+1:ii*px,1:px,kk)=color_list(ii,kk)/255; end end else end [filename, pathname] = uiputfile({'*.jpg;*.png','All Image Files';... '*.jpg','JPG';'*.png','PNG' }); imwrite(pic,[pathname,filename]); catch end end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %uh3=uimenu('label','导入'); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GUI.text=uicontrol('parent',GUI.fig,... 'style','text',... 'string','色彩识别',... 'horizontalalign','center',... 'position',[50 440 400 30],... 'backgroundcolor',[0.85 0.89 0.85],... 'foregroundcolor','k',... 'fontsize',15); GUI.text1=uicontrol('parent',GUI.fig,... 'style','text',... 'string','',... 'horizontalalign','center',... 'position',[460 330 100 100],... 'backgroundcolor',[1 1 1],... 'foregroundcolor','k',... 'fontsize',10); GUI.text2=uicontrol('parent',GUI.fig,... 'style','text',... 'string','',... 'horizontalalign','center',... 'position',[350 440 210 30],... 'backgroundcolor',[1 1 1],... 'foregroundcolor','k',... 'fontsize',10); GUI.savecolorbutton=uicontrol('parent',GUI.fig,... 'style','pushbutton',... 'string','储存颜色',... 'position',[460 290 100 30],... 'backgroundcolor',[0.85 0.89 0.85],... 'foregroundcolor','k',... 'fontsize',15,... 'callback',@save_color); GUI.deletedatabutton=uicontrol('parent',GUI.fig,... 'style','pushbutton',... 'string','清空数据',... 'position',[460 230 100 30],... 'backgroundcolor',[0.8 0.9 0.9],... 'foregroundcolor','k',... 'fontsize',15,... 'callback',@clear_data); GUI.deletepicbutton=uicontrol('parent',GUI.fig,... 'style','pushbutton',... 'string','删除图片',... 'position',[460 180 100 30],... 'backgroundcolor',[0.8 0.9 0.9],... 'foregroundcolor','k',... 'fontsize',15,... 'callback',@delete_pic); GUI.getcapbutton=uicontrol('parent',GUI.fig,... 'style','pushbutton',... 'string','屏幕截图',... 'position',[460 130 100 30],... 'backgroundcolor',[0.8 0.9 0.9],... 'foregroundcolor','k',... 'fontsize',15,... 'callback',@get_capture); GUI.getpicbutton=uicontrol('parent',GUI.fig,... 'style','pushbutton',... 'string','读取图片',... 'position',[460 80 100 30],... 'backgroundcolor',[0.8 0.9 0.9],... 'foregroundcolor','k',... 'fontsize',15,... 'callback',@getImage); GUI.getcolorbutton=uicontrol('parent',GUI.fig,... 'style','pushbutton',... 'tag','recc',... 'string','获取颜色',... 'position',[460 30 100 30],... 'backgroundcolor',[0.8 0.9 0.9],... 'foregroundcolor','k',... 'fontsize',15,... 'callback',@get_color); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% for i=1:9 GUI.text=uicontrol('parent',GUI.fig,... 'tag',num2str(i),... 'style','text',... 'string','',... 'horizontalalign','left',... 'position',[600 440-40*(i-1) 30 30],... 'backgroundcolor',[1 1 1],... 'foregroundcolor','k',... 'fontsize',10); end for i=1:9 GUI.text=uicontrol('parent',GUI.fig,... 'tag',[num2str(i),'t'],... 'style','text',... 'string','',... 'horizontalalign','center',... 'position',[640 440-40*(i-1) 150 30],... 'backgroundcolor',[1 1 1],... 'foregroundcolor','k',... 'fontsize',8); end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GUI.inputbutton=uicontrol('parent',GUI.fig,... 'style','pushbutton',... 'string','清除最后一个颜色',... 'position',[600 80 190 30],... 'backgroundcolor',[0.85 0.89 0.85],... 'foregroundcolor','k',... 'fontsize',15,... 'callback',@delete_last); %GUI.inputbutton=uicontrol('parent',GUI.fig,... %'style','pushbutton',... %'string','导出数据',... %'position',[600 30 190 30],... %'backgroundcolor',[0.85 0.89 0.85],... %'foregroundcolor','k',... %'fontsize',15,... %'callback',@output_data); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GUI.lpbutton=uicontrol('parent',GUI.fig,... 'style','pushbutton',... 'string','<上一页',... 'position',[600 30 70 30],... 'backgroundcolor',[0.85 0.85 0.85],... 'foregroundcolor','k',... 'fontsize',12,... 'callback',@lastpage); GUI.npbutton=uicontrol('parent',GUI.fig,... 'style','pushbutton',... 'string','下一页>',... 'position',[720 30 70 30],... 'backgroundcolor',[0.85 0.85 0.85],... 'foregroundcolor','k',... 'fontsize',12,... 'callback',@nextpage); GUI.page=uicontrol('parent',GUI.fig,... 'style','text',... 'string',[num2str(page),'/',num2str(total_page)],... 'horizontalalign','center',... 'position',[670 30 50 27],... 'backgroundcolor',[0.95 0.95 0.95],... 'foregroundcolor','k',... 'fontsize',12); function lastpage(~,~) page=page-1; page(page<1)=1; set(GUI.page,'string',[num2str(page),'/',num2str(total_page)]); show_color(page); end function nextpage(~,~) page=page+1; page(page>total_page)=total_page; set(GUI.page,'string',[num2str(page),'/',num2str(total_page)]); show_color(page); end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GUI.axes=axes('Units','pixels',... 'PlotBoxAspectRatio',[1 1 1],... 'Position',[50 30 400 400],... 'Color',[0.98 0.98 0.98],... 'Box','on', ... 'XLim',[0 500],... 'YLim',[0 500], ... 'XColor','w','YColor','w',... 'YDir','reverse', ... 'Tag','picbagaxes',... 'xtick',[],'ytick',[]); hold on; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %function output_data(~,~) %if color_number>1 %disp(color_list) %end %end function show_color(cur_page) len_list=size(color_list,1); for ii=(cur_page-1)*9+1:(cur_page-1)*9+9 if(ii<=len_list) set(findobj('tag',num2str(ii-(cur_page-1)*9)),'backgroundcolor',color_list(ii,:)/255) set(findobj('tag',[num2str(ii-(cur_page-1)*9),'t']),'string',['[',num2str((color_list(ii,:)/255).*rgb_type+color_list(ii,:).*(~rgb_type)),']']) else set(findobj('tag',num2str(ii-(cur_page-1)*9)),'backgroundcolor',[1 1 1]) set(findobj('tag',[num2str(ii-(cur_page-1)*9),'t']),'string','') end end end function save_color(~,~) if ~isempty(get(GUI.text2,'string')) %set(findobj('tag',num2str(color_number)),'backgroundcolor',color/255) %set(findobj('tag',[num2str(color_number),'t']),'string',['[',num2str((color/255).*rgb_type+color.*(~rgb_type)),']']) color_list(color_number,:)=color;%(color/255).*rgb_type+color.*(~rgb_type); color_number=color_number+1; if color_number-1>(total_page-1)*9 page=ceil(color_number/9); total_page=total_page+1; set(GUI.page,'string',[num2str(page),'/',num2str(total_page)]); end show_color(page) end end function delete_last(~,~) if color_number>=1 %set(findobj('tag',num2str(color_number-1)),'backgroundcolor',[1 1 1]) %set(findobj('tag',[num2str(color_number-1),'t']),'string','') color_list(end,:)=[]; color_number=color_number-1; if color_number-2<=(total_page-2)*9 page=ceil((color_number-1)/9); total_page=total_page-1; set(GUI.page,'string',[num2str(page),'/',num2str(total_page)]); end show_color(page) end end function delete_pic(~,~) control=0; set(findobj('Tag','picbagaxes'),... 'XLim',[0 500],... 'YLim',[0 500],... 'Position',[50 30 400 400],... 'Color',[0.98 0.98 0.98]); delete(a); end function clear_data(~,~) control=0; set(GUI.text1,'backgroundcolor',[1 1 1]); set(GUI.text2,'string',''); set(findobj('Tag','picbagaxes'),... 'XLim',[0 500],... 'YLim',[0 500],... 'Position',[50 30 400 400],... 'Color',[0.98 0.98 0.98]); set(findobj('tag','recc'),'string','获取颜色'); delete(a); end function get_color(~,~) if control==0 set(GUI.text1,'backgroundcolor',[1 1 1]); set(GUI.text2,'string',''); end control=1; set(gcf,'WindowButtonMotionFcn',@whilemovefcn) set(gcf,'WindowButtonDownFcn',@whileclickfcn) end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function whilemovefcn(~,~) xy=get(gca,'CurrentPoint'); x=xy(1,2);y=xy(1,1); if x<=x_limit&&y<=y_limit&&x>=0&&y>=0 x(x>x_limit)=x_limit; y(y>y_limit)=y_limit; x(x<1)=1; y(y<1)=1; x=round(x); y=round(y); if control==1 color=double([ima(x,y,1),ima(x,y,2),ima(x,y,3)]); set(GUI.text1,'backgroundcolor',color/255); set(GUI.text2,'string',['[',num2str((color/255).*rgb_type+color.*(~rgb_type)),']']); end else if control==1 set(GUI.text1,'backgroundcolor',[1 1 1]); set(GUI.text2,'string',''); end end end function whileclickfcn(~,~) xy=get(gca,'CurrentPoint'); x=xy(1,2);y=xy(1,1); if x<=x_limit&&y<=y_limit&&x>=0&&y>=0 control=0; set(GUI.text1,'backgroundcolor',color/255); set(GUI.text2,'string',['[',num2str((color/255).*rgb_type+color.*(~rgb_type)),']']); set(findobj('tag','recc'),'string','继续取色'); %disp(color/255) end end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function get_capture(~,~) screensize=get(0,'screensize'); screensize=1.5*screensize; robot=java.awt.Robot(); rectangle=java.awt.Rectangle(); rectangle.x=0; rectangle.y=0; rectangle.width=screensize(3); rectangle.height=screensize(4); image=robot.createScreenCapture(rectangle); data=image.getData(); temp=zeros(screensize(3)*screensize(4)*3,1); temp=data.getPixels(0,0,screensize(3),screensize(4),temp); temp=uint8(temp); R=temp(1:3:end); G=temp(2:3:end); B=temp(3:3:end); R=reshape(R,[screensize(3),screensize(4)]); G=reshape(G,[screensize(3),screensize(4)]); B=reshape(B,[screensize(3),screensize(4)]); R=R'; G=G'; B=B'; x_limit=screensize(4); y_limit=screensize(3); leng=max([x_limit,y_limit]); set(findobj('Tag','picbagaxes'),... 'XLim',[0 leng],... 'YLim',[0 leng]); ima=cat(3,R,G,B); delete(a); a=imshow(ima); end function getImage(~,~) warning off; try [filename, pathname] = uigetfile({'*.jpg;*.tif;*.png;*.gif','All Image Files';... '*.*','All Files' }); ima = imread([ pathname,filename]); [x,y,~]=size(ima); x_limit=x;y_limit=y; leng=max([x_limit,y_limit]); set(findobj('Tag','picbagaxes'),... 'XLim',[0 leng],... 'YLim',[0 leng]); delete(a); a=imshow(ima); catch end end end
颜色转换与色差计算
用 RGB 比较颜色之间的相似度时,存在很大的问题,不建议直接使用,因为往往一个通道的一点改变,会导致最后融合在一起的颜色发生巨大变化,而如果三个通道的同时改变,却只会使最后的明暗发生变化,色调并不会产生巨大变化。而这也是H系列色彩空间普遍存在的问题。
所以,经过思考,我决定 RGB 的值转为 HSV 颜色空间,再进行色差计算。
色差计算,直接利用两个点的颜色在椎体上的欧式距离,即通过计算如下坐标点的欧式距离。我们通过编写 color_dist 函数计算。
%计算两个 HSV 颜色之间的距离 function d = color_dist(color_std,color) [x0,y0,z0] = getPos(color_std(1),color_std(2),color_std(3)); [x,y,z] = getPos(color(1),color(2),color(3)); d = sqrt((x-x0).^2+(y-y0).^2+(z-z0)^2); end function [x,y,z] = getPos(H,S,V) r = 1; h = sqrt(3); x = r*V*S*cos(H); y = r*V*S*sin(H); z = h*(1-V); end
%% 颜色转为 HSV,再进行色差计算 A2 = rgb2hsv(A); color2 = rgb2hsv(color); D = ones(low_num,col_num)*2; for i=1:low_num for j=1:col_num D(i,j) = color_dist(color2,A2(i,j,:)); end end mesh(D);
分离曲线
通过调节阈值参数,可以把我们想要的坐标轴过滤掉。阈值越小,过滤效果越明显。
%% 根据色彩,把想要的曲线分离出来 threshold = 0.5;%可以调整阈值使分离效果变好 I = (D<threshold); for i=1:3 RGB= A(:,:,i); RGB(~I)=255; A3(:,:,i) = RGB; end imshow(A3)
二值化,提取数据
二值化图像是以矩阵形式存储的。我们根据色素点在矩阵中的位置,利用行列指标和坐标轴的标准化关系,提取数据,重建坐标系。
%% 二值化,提取数据 leftUp = [0,1];%标注截取的图片的左上角和右下角 rightDown = [1,0]; B = rgb2gray(A3); imshow(B); [pos_row,pow_col] = find(B~=255); Ps = [pos_row-1,pow_col-1]; Ps(:,1) = Ps(:,1)./(low_num-1); Ps(:,2) = Ps(:,2)./(col_num-1); x = (rightDown(1)-leftUp(1)).*Ps(:,2)+leftUp(1); y = leftUp(2)-abs(rightDown(2)-leftUp(2)).*Ps(:,1); scatter(x,y,0.38); X = [x,y];
看着八九不离十了,但是注意这里的坐标点是以散点的形式画出来的。是无序且没有区分度的,我们甚至无法使用plot
。
数据点分类与排序
肉眼可见,这几条曲线是分隔开的。我们如何把这些数据按曲线分开且其上的点按顺序排好呢?我的做法是,使用鼠标选中你在意的曲线的一端,利用距离延拓法,还原整条曲线。
%% 数据分类与排序 N = size(X,1); gfrom = ginput(1); [~,minI] = min(sum((X - repmat(gfrom,N,1)).^2,2)); from = X(minI,:); X(minI,:) = []; X = [from;X]; tol = 0.01; X1 = findcurvepath(X,tol); plot(X1(:,1),X1(:,2))
其中用到了一个自己写函数 findcurvepath
,它可以从一个数据点出发,把所有的数据点按距离远近,像串珍珠一样串在一块。
function ps1 = findcurvepath(ps0,tol) %这个函数将点就近连接起来 ps1(1,1:2) = ps0(1,1:2);%将第一个取出来 ps0 = ps0(2:end,:);%ps0重置为剩下的部分 p = ps1(end,:);%p是取出来的最后一个 while ~isempty(ps0) i = 1; while 1 distances = p2psdistance(p,ps0); inds = find(distances == min(distances));%%%%%%%修复一个bug if length(inds) > 1 if size(ps1,1)<i+1 break; end p = ps1(end-i,:);%p是取出来的最后一个 i = i+1; else break; end end distances = p2psdistance(p,ps0); if min(distances)>tol break; end inds = find(distances == min(distances));%%%%%%%修复一个bug ind = inds(1); ps1(end+1,1:2) = ps0(ind,1:2); ps0(ind,:) = []; p = ps1(end,:); end end function distances = p2psdistance(p,ps) distances = sqrt((p(1)-ps(:,1)).^2+(p(2)-ps(:,2)).^2); end
后话
我不喜欢做 GUI,因为乱七八糟的界面用起来确实令人心烦。另外一方面,不管是 GUI 或者说进一步的 exe 桌面程序,都依赖于 MATLAB 的环境。也就是说,你要运行代码,就必须先装 MATLAB 的环境。试想,一个安装过和简单用过 MATLAB 的人,都是有能力可以通过代码修改一些参数的,那么你做 GUI 不是画蛇添足么。
到此这篇关于利用Matlab提取图片曲线的文章就介绍到这了,更多相关Matlab提取图片曲线内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
推荐阅读
-
用 Matlab 和 Python 方法查找零极点图、系统函数的频率响应(幅频特性、相频特性
-
纯干货分享 | 研发效能提升——敏捷需求篇-而敏捷需求是提升效能的方式中不可或缺的模块之一。 云智慧的敏捷教练——Iris Xu近期在公司做了一场分享,主题为「敏捷需求挖掘和组织方法,交付更高业务价值的产品」。Iris具有丰富的团队敏捷转型实施经验,完成了企业多个团队从传统模式到敏捷转型的落地和实施,积淀了很多的经验。 这次分享主要包含以下2个部分: 第一部分是用户影响地图 第二部分是事件驱动的业务分析Event driven business analysis(以下简称EDBA) 用户影响地图,是一种从业务目标到产品需求映射的需求挖掘和组织的方法。 在软件开发过程中可能会遇到一些问题,比如大家使用不同的业务语言、技术语言,造成角色间的沟通阻碍,还会导致一些问题,比如需求误解、需求传递错误等;这会直接导致产品的功能需求和要实现的业务目标不是映射关系。 但在交付期间,研发人员必须要将这些需求实现交付,他们实则并不清楚这些功能需求产生的原因是什么、要解决客户的哪些痛点。研发人员往往只是拿到了解决方案,需要把它实现,但没有和业务侧一起去思考解决方案是否正确,能否真正的帮助客户解决问题。而用户影响地图通常是能够连接业务目标和产品功能的一种手段。 我们在每次迭代里加入的假设,也就是功能需求。首先把它先实现,再逐步去验证我们每一个小目标是否已经实现,再看下一个目标要是什么。那影响地图就是在这个过程中帮我们不断地去梳理目标和功能之间的关系。 我们在软件开发中可能存在的一些问题 针对这些问题,我们如何避免?先简单介绍做敏捷转型的常规思路: 先做团队级的敏捷,首先把产品、开发、测试人员,还有一些更后端的人员比如交互运维的同学放在一起,组成一个特训团队做交付。这个团队要包含交付过程中所涉及的所有角色。 接着业务敏捷要打通整个业务环节和研发侧的一个交付。上图中可以看到在敏捷中需求是分层管理的,第一层是业务需求,在这个层级是以用户目标和业务目标作为输入进行规划,同时需要去考虑客户的诉求。业务人员通过获取到的业务需求,进一步的和团队一起将其分解为产品需求。所以业务需求其实是我们真正去发布和运营的单元,它可以被独立发布到我们的生产环境上。我们的产品需求其实就是产品的具体功能,它是我们集成和测试的对象,也就是我们最终去部署到系统上的一个基本单元。产品需求再到了我们的开发团队,映射到迭代计划会上要把它分解为相应的技术任务,包括我们平时所说的比如一些前端的开发、后端的开发、测试都是相应的技术任务。所以业务敏捷要达到的目标是需要去持续顺畅高质量的交付业务价值。 将这几个点串起来,形成金字塔结构。最上层我们会把业务目标放在整个金字塔的塔尖。这个业务目标是通过用户的目标以及北极星指标确立的。确认业务目标后再去梳理相应的业务流程,最后生产。另外产品需求包含了操作流程和业务规则,具需求交付时间、工程时间以及我们的一些质量标准的要求。 谈到用户影响的地图,在敏捷江湖上其实有一个传说,大家都有一个说法叫做敏捷需求的“任督二脉”。用户影响地图其实就是任脉,在黑客马拉松上用过的用户故事地图其实叫督脉。所以说用户影响地图是在用户故事地图之前,先帮我们去梳理出我们要做哪些东西。当我们真正识别出我们要实现的业务活动之后,用户故事地图才去梳理我们整个的业务工作流,以及每个工作流节点下所要包含的具体功能和用户故事。所以说用户影响地图需要解决的问题,我们包括以下这些: 首先是范围蔓延,我们在整张地图上,功能和对应的业务目标是要去有一个映射的。这就避免了一些在我们比如有很多干系人参与的会议上,那大家都有不同想法些立场,会提出很多需求(正确以及错误的需求)。这个时候我们会依据目标去看这些需求是否真的是会影响我们的目标。 这里提到的错误需求,比如是利益相关的人提出的、客户认为产品应该有的、某个产品经理需求分析师认为可以有的....但是这些功能在用户影响地图中匹配不到对应目标的话,就需要降低优先级或弃掉。另外,通常我们去制定解决方案的时候,会考虑较完美的实现,导致解决方案括很多的功能。这个时候关键目标至关重要,会帮助我们梳理筛选、确定优先级。 看一下用户影响到地图概貌 总共分为一个三层的结构: 第一层why,你的业务目标哪个是最重要的,为什么?涉及到的角色有哪些? 第二层how ,怎样产生影响?影响用户角色什么样的行为? (不需要去列出所有的影响,基于业务目标) 第三层what,最关键的是在梳理需求时不需一次把所有细节想全,这通常团队中经常遇到的问题。 我们用这个例子来看一下 这是一个客服中心的影响地图,业务目标是 3个月内不增加客服人数的前提下能支持1.5倍的用户数。此业务目标设定是符合 smart 原则的,specific非常的具体,miserable 是可以衡量的,action reoriented是面向活动的, real list 也是很实际的。 量化的目标会指引我们接下来的行动,梳理一个业务目标,尽量去量化,比如 :我们通过打造一条什么样的流水线,能够提高整个部署的效率,时间是原来的 1/2 。这样才是一个能量化的有意义的目标。 回到这幅图, how 层级识别出来的内容,客服角色:想要对它施加的影响,把客户引导到论坛上,帮助客户更容易的跟踪问题,更快速的去定位问题。初级用户:方论坛上找到问题。高级用户:在论坛上回答问题。通过我们这些用户角色,进行活动,完成在不增加客户客服人数的前提下支持更多的用户数量。 最后一个层级,才是我们日常接触比较多的真正的功能的特性和需求,比如引导到客户到论坛上,其实这个产品就需要有一个常见问题的论坛的链接。这个层次需要我们团队进一步地在交付,在每个迭代之前做进一步的梳理,细化成相应的用户故事。 这个是云智慧团队中,自己做的影响地图的范例,可以看下整个的层级结构。序号表示优先级。 那我们用户影响地图可以总结为:
-
用Matlab绘制曲线基础函数的方法
-
Grid++Report 锐浪报表开发常见问题解答集锦-报表设计 问:怎样在设计时打印预览报表? 答:为了及时查看报表的设计效果,Grid++Report 报表设计应用程序提供了四种查看视图:普通视图、页面视图、预览视图与查询视图。通过窗口下边的 Tab 按钮可以在四种视图中任意切换。在预览视图中查看报表的打印预览效果,在查询视图中查看报表的查询显示效果。如果在报表的记录集提供了数据源连接串与查询 SQL,在进入预览视图与查询视图时会利用数据源连接串与查询 SQL 从数据源中自动取数,否则 Grid++Report 将自动生成模拟数据进行模拟打印预览与查询显示。注意:在预览视图与查询视图中看到的报表运行结果有可能与在你程序中的最终运行结果有差异,因为在报表的生成过程中我们可以在程序中对报表的生成行为进行一定的控制。 问:怎样用 Grid++Report 设计交叉表? 答:Grid++Report 没有提供专门实现交叉表的功能,其它的报表构件提供的交叉表功能一般也比较死板和功能有限。利用 Grid++Report 的编程接口可以做出灵活多变,功能丰富的交叉表。示例程序 CrossTab 就是一个实现交叉表的例子程序,认真领会此例子程序,你就可以做出自己想要各种交叉表,并能提取一些共用代码,便于重复使用。 问:怎样设置整个报表的缺省字体? 答:设置报表主对象的字体属性,也就是设置了整个报表的缺省字体。如果改变报表主对象的字体属性,则没有专门的设置字体属性的子对象的字体属性也跟随改变。同样每个报表节与明细网格也有字体属性,他们的字体属性也就是其拥有的子对象的缺省字体。 问:怎样在打印时限制一页的输出行数? 答:设定明细网格的内容行的‘每页行数(RowsPerPage)’属性即可。另外要注意‘调节行高(AdjustRowHeight)’属性值:为真时根据页面的输出高度自动调整行的高度,使整个页面的输出区域充满。为假时按设计时的高度输出行。 问:怎样显示中文大写金额? 答:将对象的“格式(Format)”属性设为 “$$” 及可,可以设置格式的对象有:字段(IGRField)、参数(IGRParameter)、系统变量(IGRSystemVarBox)与综合文字框(IGRMemoBox),其中综合文字框是在报表式上设格式。 问:能否实现自定义纸张与票据打印? 答:Grid++Report 完全支持自定义纸张的打印,只要在报表设定时在页面设置中选定自定义纸张,并指定准确的纸张尺寸。当然要在最终输出时得道合适的打印结果,输出打印机必须支持自定义纸张打印。Windows2000/XP/2003 操作系统上可以在打印机上定义自定义纸张,也可以采用这种方式实现自定义纸张打印。 问:怎样实现 0 值不打印? 答:直接设置格式串就可以,在“数字格式”设置对话框中选定“0 不显示”,就会得到合适的格式串。也可以通过直接录入格式串来指定 0 不显示,但格式串必须符合 Grid++Report 的规定格式。另一种实现办法是在报表获取明细记录数据时,在 BeforePostRecord 事件中将值为零的字段设为空,调用字段的 Clear 方法将字段置为空。 问:怎样实现多栏报表? 答:在明细网格上设‘页栏数(PageColumnCount)’属性值大于 1 即可。通过 Grid++Report 的“页栏输出顺序”还可以指定多栏报表的输出顺序是“先从上到下”还是“先从左到右”。 问:如何实现票据套打? 答:Grid++Report 为实现票据套打做了很多专门的安排:报表设计器提供了页面设计模式,按照设定的纸张尺寸显示设计面板,如果将空白票据的扫描图设为设计背景图,在定位报表内容的输出位置会非常方便。报表部件可以设定打印类别,非套打输出的内容在套打打印模式下就不会输出。 问:Grid++Report 有没有横向分页功能? 答:回答是肯定的,在列的总宽度超过打印页面的输出宽度时,Grid++Report 可以另起新页输出剩余的列,如果左边存在锁定列,锁定列可以在后面的新页中重复输出,这样可以保证关键数据列在每一页都有输出。仔细体会 Grid++Report 提供的多种打印适应策略,选用最合适的方式。Grid++Report 的多种打印适应策略为开发动态报表提供了很好的支持。 问:怎样实现报表本页小计功能? 答:定义一个报表分组,将本分组定义为页分组,在本分组的分组头与分组尾上定义统计。页分组就是在每页产生一个分组项,在每页的上端与下端都会分别显示页分组的分组头与分组尾,页分组不用定义分组依据字段。 报表运行 问:怎样与数据库建立连接? 答:如果在设计报表时指定了数据集的数据源连接串与查询 SQL 语句,Grid++Report 采用拉模式直接从数据源取得报表数据,Grid++Report 利用 OLE DB 从数据源取数,OLE DB 提供了广泛的数据源操作能力。如果 Grid++Report 的数据来源采用推模式,即 Grid++Report 不直接与数据库建立连接,各种编程语言/平台都提供了很好的数据库连接方式,并且易于操作,应用程序在报表主对象(IGridppReport)的 FetchRecord 事件中将数据传入,例子程序提供了各种编程语言填入数据的通用方法,对C++Builder 和 Delphi 还进行了专门的包装,直接关联 TDataSet 对象也可以将 TDataSet 对象中的数据传给报表。 问:打印时能否对打印纸张进行自适应?支持表格的折行打印吗? 答:Grid++Report 在打印时采用多种适应策略,通过设置明细网格(IGRDetailGrid)的‘打印策略(PrintAdaptMethod)’属性指定打印策略。(1)丢弃:按设计时列的宽度输出,超出范围的内容不显示。(2)绕行:按设计时列的宽度输出,如果在当前行不能完整输出,则另起新行进行输出。(3)缩放适应:对所有列的输出宽度进行按比例地缩放,使总宽度等于页面的输出宽度。(4)缩小适应:如果列的总宽度小于页面的输出宽度,对所有列的输出宽度进行按比例地缩小,使总宽度等于页面的输出宽度。(5)横向分页:超范围的列在新页中输出。(6)横向分页并重复锁定列。 问:如何改变缺省打印预览窗口的窗口标题? 答:改变报表主对象的‘标题(Title)’属性即可。 问:利用集合对象的编程接口取子对象的接口引用,但不是自己期望的结果。 答:Grid++Report中所有集合对象的下标索引都是从 1 开始,另按对象的名称查找对象的接口引用时,名称字符是不区分大小写的。 问:怎样在运行时控制报表中各个对象的可见性?即怎样在运行时显示或隐藏对象? 答:在报表主对象(GridppReport)的 SectionFormat 事件中设定相应报表子对象的可见(Visible)属性即可。 问:报表主对象重新载入数据,设计器中为什么没有反映新载入的数据? 答:应调用 IGRDesigner 的 Reload 方法。 问:怎样实现不进入打印预览界面,直接将报表打印出来?
-
用Matlab从图片中取出曲线的方法