小字符图像处理
2018-01-16 20:14:00 weixin_34270606 阅读数 9

梯度/梯度算子:这里的梯度特指二维离散函数中的梯度,因此就不能用连续函数的算法计算,而是要用差分的方法。计算方法有很多种,常见的两种其一是G(x,y) = dx(i,j) + dy(i,j); 其中dx(i,j) = H(i+1,j) - H(i,j); dy(i,j) = H(i,j+1) - H(i,j);,或者使用中值差分,即dx(i,j) = (H(i+1,j) - H(i-1,j)) / 2; dy(i,j) = (H(i,j+1) - H(i,j-1)) / 2;。在图像处理中,计算梯度通常使用卷积的方式来求得,卷积核被称为算子。通常一阶导数算子(梯度算子)用于在边缘灰度值过度比较尖锐、图像噪声比较小时,而二阶导数算子(拉普拉斯算子)用于确定已知边缘像素后像素在明区还是暗区。

Hough变换:把图像平面上的点对应到参数平面上的线,最后通过统计特性(假如是直线就统计线的交点个数,参数平面下交点的坐标就是斜率和截距,对应图像平面就是一条线)

2015-07-28 10:06:15 u014616233 阅读数 391
po主从今天开始做图像匹配方面的研究,每天把自己学到的新东西放上来,当做是记录自己成长的足迹吧,加油
2017-07-03 10:47:00 weixin_34187862 阅读数 21
一、界面学习
  用java实现一个简易计算器(代码)如下:
1 /*CJSCalculator.java 2014.8.4 by cjs
2  *当点击含有加号的按钮时,则第一排第二个按钮的文本变为加号;
3  *当点击“OK”按钮时,将算出12+2的结果并在第一排最后一个按钮显示;
4  *减号,乘号,除号的功能类似。其中,数字可以自己输入,也可以固定不变。
5  *以上是简单的版本,如果有能力可以设计出更好更完善的计算器。
6 **/
7
8 import java.awt.*;
9 import javax.swing.*;
10 import java.awt.event.*;
11 public class CjsCalculator extends JFrame implements ActionListener {
12     /* 继承Jframe 实现 ActionListener 接口*/
13
14         //协助关闭窗口
15         private class WindowCloser extends WindowAdapter {
16         public void windowClosing(WindowEvent we) {
17             System.exit(0);
18         }
19     }
20     //strings for operator buttons.
21
22     private String[] str = { "+", "-", "*", "/", "OK"};
23
24     //build buttons.
25
26     JButton[] Obuttons = new JButton[str.length];
27         //reset button
28     JButton Rbutton = new JButton("reset");
29
30         //build textfield to show num and result
31
32     private JTextField display = new JTextField("0");
33     private JTextField Fnum = new JTextField("");
34     private JTextField Snum = new JTextField("");
35     private JTextField Otext = new JTextField("");
36     private JTextField Deng = new JTextField("=");
37
38     int i = 0;
39
40     //构造函数定义界面
41     public CjsCalculator() {
42
43         Deng.setEditable(false);
44         display.setEditable(false);
45     Otext.setEditable(false);
46     //super 父类
47     //    super("Calculator");
48
49         //panel 面板容器
50         JPanel panel1 = new JPanel(new GridLayout(1,5));
51         for (i = 0; i < str.length; i++) {
52             Obuttons[i] = new JButton(str[i]);
53                 Obuttons[i].setBackground(Color.YELLOW);
54             panel1.add(Obuttons[i]);
55         }
56
57         JPanel panel2 = new JPanel(new GridLayout(1,5));
58         panel2.add(Fnum);
59         panel2.add(Otext);
60         panel2.add(Snum);
61         panel2.add(Deng);
62         panel2.add(display);
63
64         JPanel panel3 = new JPanel(new GridLayout(1,1));
65         panel3.add(Rbutton);
66                 //初始化容器
67         getContentPane().setLayout(new BorderLayout());
68         getContentPane().add("North",panel2);
69         getContentPane().add("Center",panel1);
70         getContentPane().add("South",panel3);
71         //Add listener for Obuttons.
72         for (i = 0; i < str.length; i++)
73             Obuttons[i].addActionListener(this);
74
75         display.addActionListener(this);
76             Rbutton.addActionListener(this);
77         setSize(8000,8000);//don't use ???
78
79         setVisible(true);//???
80                 //不可改变大小
81                 setResizable(false);
82         //初始化容器
83         pack();
84     }
85
86         //实现监听器的performed函数
87     public void actionPerformed(ActionEvent e) {
88                 Object happen = e.getSource();
89         //
90         String label = e.getActionCommand();
91
92         if ("+-*/".indexOf(label) >= 0)
93             getOperator(label);
94         else if (label == "OK")
95             getEnd(label);
96         else if ("reset".indexOf(label) >= 0)
97              // display.setText("reset");
98                       resetAll(label);
99     }
00         public void resetAll(String key) {
101             Fnum.setText("");
102         Snum.setText("");
103         display.setText("");
104         Otext.setText("");
105     }
106     public void getOperator(String key) {
107         Otext.setText(key);
108     }
109
110     public void getEnd(String label) {
111                 if( (countDot(Fnum.getText()) > 1) || (countDot(Snum.getText())>1) || (Fnum.getText().length()==0) ||
(Snum.getText().length() == 0)) {
112                     display.setText("error");
113                 }
114         else if(checkNum(Fnum.getText())==false || checkNum(Snum.getText())==false){
115                 display.setText("error");
116              }
117         else {
118                 double Fnumber = Double.parseDouble(Fnum.getText().trim());
119             double Snumber = Double.parseDouble(Snum.getText().trim());
120             if (Fnum.getText() != "" && Snum.getText() != "") {
121                    if (Otext.getText().indexOf("+") >= 0) {
122                     double CjsEnd = Fnumber + Snumber;
123                     display.setText(String.valueOf(CjsEnd));
124                 }
125                                 else if (Otext.getText().indexOf("-")>=0) {
126                                        double CjsEnd = Fnumber - Snumber;
127                                         display.setText(String.valueOf(CjsEnd));
128                                 }
129                         else if (Otext.getText().indexOf("*")>=0) {
130                                         double CjsEnd = Fnumber * Snumber;
131                                            display.setText(String.valueOf(CjsEnd));
132                                 }
133                                 else if (Otext.getText().indexOf("/")>=0) {
134                                         double CjsEnd = Fnumber / Snumber;
135                                         display.setText(String.valueOf(CjsEnd));
136                                 }
137                 else
138                     display.setText("error");
139
140             }
141             else
142                 display.setText("num is null");
143                 }
144
145     }
146         public int countDot(String str) {
147         int count = 0;
148         for (char c:str.toCharArray()) {
149             if (c == '.')
150                 count++;
151         }
152         return count;
153         }
154         public boolean checkNum(String str) {
155            boolean tmp = true;
156         for (char c:str.toCharArray()) {
157             if (Character.isDigit(c) || (c == '.'));
158             else {
159                 tmp = false;
160                 break;
161             }
162         }
163         return tmp;
164         }
165     public static void main(String[] args) {
166         new CjsCalculator();
167     }
168 }
  终端运行该java文件,结果如图所示:

最新内容请见作者的GitHub页:http://qaseven.github.io/
2018-01-10 13:27:17 FloatDreamed 阅读数 582
附上代码

 

#   _*_ coding:utf-8 _*_
from PIL import Image

__author__ = 'admin'

def transform(im):
    im = im.convert("L")
    #   初始化txt
    txt = ""
    for i in range(im.size[1]):
        for j in range(im.size[0]):
            grey = im.getpixel((j, i))
            #   坐标处的灰度值越小,说明灰的程序深,对应替换的字符应该越靠在ascii_char的前面
            txt += ascii_char[int(grey / unit)]
        txt += '\n'
    return txt

#   ascii字符列表(事先排序过的,为了效果)
ascii_char = list(r"$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")
#   ascii的字符的总长度
length = len(ascii_char)
#   灰度值与ascii_char中的字符转换时的系数,保证灰度值转换字符时不会超出列表ascii_char
unit = 256.0 / length
#   打开目标图片
im = Image.open(r"C:\Users\admin\Desktop\lufei1.jpg")
#   获取图片的大小:(1024, 768)
width, height = im.size
#   将图片进行一定比例的缩小(即重设图片大小)
im = im.resize((int(width * 1.1), int(height * 0.6)))
print("重新生成的图像大小为:%d*%d" % (im.size[0], im.size[1]))


txt = transform(im)
f = open(r"C:\Users\admin\Desktop\characterdrawing1.txt", 'w')
f.write(txt)
f.close()

 

 

 

以下就是原图与效果图的对比:

1.晴明

2.神乐

3.路飞

2011-12-25 21:28:54 qjzl2008 阅读数 572
做个小结来回顾下图像处理基础的一些小问题。

这次做的图像处理主要还是处理的8位的BMP图像,二值化和素描功能模块。

BMP文件总体上由4部分组成,分别是位图文件头、位图信息头、调色板和图像数据

1.位图文件头位图文件头包含了图像类型、图像大小、图像数据存放地址和两个保留未使用的字段。

typedef struct tagBITMAPFILEHEADER { 
WORD    bfType; 			//位图类别,根据不同的操作系统而不同,在Windows中,此字段的值总为‘BM’
DWORD   bfSize; 			//BMP图像文件的大小
WORD    bfReserved1; 
WORD    bfReserved2; 
DWORD   bfOffBits; 		//BMP图像数据的地址,也即离文件头的偏移量
} BITMAPFILEHEADER

2.位图信息头

位图信息头包含了位图信息头的大小、图像的宽高、图像的色深、压缩说明图像数据的大小和其他一些参数。

typedef struct tagBITMAPINFOHEADER{ 
DWORD      biSize; 	//本结构的大小,根据不同的操作系统而不同,在Windows中,此字段的值总为28h字节=40字节
LONG        biWidth; 	//BMP图像的宽度,单位像素
LONG        biHeight; 
WORD       biPlanes; 
WORD       biBitCount; 	//BMP图像的色深,即一个像素用多少位表示,常见有1、4、8、16、24和32,分别对应单色、16色、256色、16位				  高彩色、24位真彩色和32位增强型真彩色
DWORD      biCompression;   //压缩方式,0表示不压缩,1表示RLE8压缩,2表示RLE4压缩,3表示每个像素值由指定的掩码决定
DWORD      biSizeImage;   //BMP图像数据大小,必须是4的倍数,图像数据大小不是4的倍数时用0填充补足
LONG        biXPelsPerMeter; 	//水平分辨率,单位像素/m
LONG        biYPelsPerMeter; 	//垂直分辨率,单位像素/m
DWORD      biClrUsed; 		//BMP图像使用的颜色,0表示使用全部颜色,对于256色位图来说,此值为100h=256
DWORD      biClrImportant; 	//重要的颜色数,此值为0时所有颜色都重要,对于使用调色板的BMP图像来说,当显卡不能够显示所有颜色时,此值将辅助驱动程序显示颜色
} BITMAPINFOHEADER,

3.彩色表/调色板

彩色表/调色板是单色、16色和256色图像文件所特有的,相对应的调色板大小是2、16和256,调色板以4字节为单位,每4个字节存放一个颜色值,图像 的数据是指向调色板的索引。

typedef struct tagRGBQUAD { 
BYTE    rgbBlue; 			//蓝色值
BYTE    rgbGreen; 		        //绿色值
BYTE    rgbRed; 			//红色值
BYTE    rgbReserved; 		        //保留,总为0
} RGBQUAD;

4.位图数据

如果图像是单色、16色和256色,则紧跟着调色板的是位图数据,位图数据是指向调色板的索引序号。

如果位图是16位、24位和32位色,则图像文件中不保留调色板,即不存在调色板,图像的颜色直接在位图数据中给出。

16位图像使用2字节保存颜色值,常见有两种格式:5位红5位绿5位蓝和5位红6位绿5位蓝,即555格式和565格式。555格式只使用了15 位,最后一位保留,设为0。

24位图像使用3字节保存颜色值,每一个字节代表一种颜色,按红、绿、蓝排列。

32位图像使用4字节保存颜色值,每一个字节代表一种颜色,除了原来的红、绿、蓝,还有Alpha通道,即透明色。

同时,还有一种结构,由位图信息头和颜色表组成位图信息结构BITMAPINFO

typedef struct tagBITMAPINFO{
BITMAPINFOHEADER bmiHeader;		//位图信息头
RGBQUAD bmiColors[1];           	 //颜色表
}BITMAPINFO;

在我的图像处理当中,为方便处理图像数据,于是定义了一个CMyImage类,用于存放相关数据

class CMyImage
{
public:
	CMyImage(void);
	~CMyImage(void);
	HANDLE   		hdib;		//用于存放需要操作的图像内存地址
	BITMAPINFO		*lpbi;		//指向一个BITMAPINFO结构的一个指针
  	LPSTR    		lpbits;		//指向实际图像数据
	DWORD    		wid;		//图像的宽度
	DWORD    		hei;		//图像的高度
	WORD     		bitcount;	//每像素位数
	WORD     		bpl;		//每行字节数
	WORD     		bpp;		//每像素字节数
};



使用图像时候那么就需要在内存中申请空间来存储空间进而进行操作

image->hdib=GlobalAlloc(GPTR, size); 	  //  申请内存空间
image->lpbi=(BITMAPINFO *) GlobalLock(image->hdib);	 //  得位图信息指针
image->lpbits=(LPSTR) image->lpbi + offset; 	        //  得DIB中像素数据起始位置

在申请内存空间前MyImage的成员数据也就需要赋值了

image->bpp=(WORD) ((bitcount+7)/8); 			 //  计算每像素字节数
image->bpl=(WORD) ((wid*bitcount+31)/32*4);	                                        //  计算每行字节数
size=40+palette+image->bpl*hei+64; 	                                              //  计算DIB尺寸

既然每使用一次新的图像就需要创建一个MyImage,那么内存回收的问题也就显得更为重要,一不小心造成内存泄露,那么想找出根源也就很苦恼了。

所以,每当要创建一个新的图像时,那么需要检查一下看是否需要先释放内存

if(image->hdib != NULL)
{   
   GlobalUnlock(image->hdib);   //解除图像在内存中的锁定
   GlobalFree(image->hdib);	//  释放内存空间
   image->hdib=NULL;	//  指针清零
}

开始进行操作,图像二值化,基本上就是把图像先进行灰度处理,转化成灰度图像,然后选举合适的阈值再进行处理。灰度大于或等于阀值的像素被判定为属于特定物体,其灰度值为255表示,否则这些像素点被排除在物体区域以外,灰度值为0。( 灰度值255即白色,灰度值0即黑色)


那么二值化处理的主要不同就是在选举合适的阈值上。常见有大津法,迭代法,双峰法。在取阈值前,通常要获取到图像的直方图,统计图像灰度值从0-255每个灰度值中的个数


大津法取阈值

对图像Image,记t为前景与背景的分割阈值,前景点数占图像比例为w0,平均灰度为u0;背景点数占图像比例为w1,平均灰度为u1。图像的总平均灰度为:u=w0*u0+w1*u1。从最小灰度值到最大灰度值遍历t,当t使得值g=w0*(u0-u)2+w1*(u1-u)2 最大时t即为分割的最佳阈值。对大津法可作如下理解:该式实际上就是类间方差值,阈值t分割出的前景和背景两部分构成了整幅图像,而前景取值u0,概率为 w0,背景取值u1,概率为w1,总均值为u,根据方差的定义即得该式。

代码实现:

int  CThreShold::Otsu(long *pg)         //  大津法取阈值   pg[256] 存放索引对应灰度值数量
{
   int  i,j,p;
   double A,B,An,Bn,u,v,qqq[256],max,min;

   An=Bn=0;
   for (i=0;i<256;i++)
   {
      An+=pg[i];     Bn+=pg[i]*(i+1);          
   }
   for (j=0;j<256;j++)
   {
      A=B=0;
      for (i=0;i<=j;i++) 
	  {
		 A+=pg[i];	 B+=pg[i]*(i+1);
      }
      if (A) u=B/A;
      else   u=0;
      if (An-A) v=(Bn-B)/(An-A);
      else      v=0;
      qqq[j]=A*(An-A)*(u-v)*(u-v);  
   }

   max=min=qqq[0];		p=0;
   for (i=1;i<256;i++) 
   {            
      if (qqq[i]>max) 
	  {
	     max=qqq[i];
	     p=i;
      }
	  else if (qqq[i]<min) min=qqq[i];
   }
   return(p);
}

迭代法取阈值

迭代法是基于逼近的思想,其步骤如下:
1.  求出图象的最大灰度值和最小灰度值,分别记为ZMAX和ZMIN,令初始阈值T0=(ZMAX+ZMIN)/2;
2.  根据阈值TK将图象分割为前景和背景,分别求出两者的平均灰度值ZO和ZB;
3.  求出新阈值TK+1=(ZO+ZB)/2;
4.  若TK=TK+1,则所得即为阈值;否则转2,迭代计算


代码实现:

int  CThreShold::Iterative(long *pg)
 {
	 int i;
	 long nZmax,nZmin,nTFirst,temp,nT;
	 long Tsum = 0,Tnum =0,averHead,averTail;
	 for(i =0;pg[i] == 0;i++)
			break;
	 nZmin = static_cast<long>(i);
	 for(i = 255;pg[i] ==0;i--)
			break;
	 nZmax = static_cast<long>(i);
	 nTFirst = (nZmin+nZmax)/2;
	 nT = nTFirst;
	 temp = nT;
	 while(nT != temp)
	 {
		 temp = nT;
		 for(i=0;i<nT;i++)
		 {
			 Tsum+=pg[i]*i;
			 Tnum+=pg[i];
		 }
		 averHead = Tsum/Tnum;
		 Tsum = 0;
		 Tnum = 0;

		 for(i=nT;i<256;i++)
		 {
			 Tsum+=pg[i]*i;
			 Tnum+=pg[i];
		 }
		 averTail = Tsum/Tnum;
		 Tsum = 0;
		 Tnum = 0;
		 nT = (averHead+averTail)/2;
	 }

	 return nT;
 }

双峰法取阈值

双峰法的原理:它认为图像由前景和背景组成,在灰度直方图上,前后二景都形成高峰,在双峰之间的最低谷处就是图像的阈值所在。从分割的效果来看,当前后景的对比较为强烈时,分割效果较好;否则基本无效。

代码实现:

 int  CThreShold::DoublePeak(long *pg,int width)
 {
     long nPeakS = 0, nPeakE = 0,nValley = 0;
     int nSID = 0,nEID =0,nVID =0,i = 0;
     for(i =0;i<255;i++)
     {
	if(nPeakS<pg[i])
	{
	    nPeakS = pg[i];
	    nSID = i;
	}			
     }
     if( nSID-width>0)
     {
	for(i =0;i<nSID-width;i++)
	{
	    if(nPeakE<pg[i])
	    {
		 nPeakE = pg[i];
		 nEID = i;
	    }
	}
     }
     if( nSID- width<255)
     {
	 for(i =nSID+width;i<256;i++)
	{
	     if(nPeakE<pg[i])
	     {
		nPeakE = pg[i];
		nEID = i;
	     }
	}
      }
     nValley = pg[nSID];
     int t1 = nSID,t2 = nEID;
     if(nSID>nEID)
     {
	t1 = nEID;
	t2 = nSID;
     }
     for(i = t1;i<t2;i++)
    {
	if(pg[i]<nValley)
	{
	    nValley = pg[i];
	    nVID = i;
	}
    }
     return nVID;
 }

素描功能,主要采用的Laplacian算子的素描功能,使用的8邻点计算。

g(i,j) = 8 f(i,j) -f(i-1,j) -f(i+1,j)-f(i,j-1) -f(i,j+1)- f(i-1,j-1)-f(i-1,j+1)-f(i+1,j-1)-f(i+1,j+1)+offset

其中offset=255。当g(x)>255时 g(x) =255,当g(x)<0时,g(x) = 0;

这样处理后有素描效果。

代码实现:

void CLaplacianSketch::Template(BYTE **list1,BYTE **list0,int Dx,int Dy)
{
  int  i,j,g;
  
  for (i=1;i<Dy-1;i++) 
  {
	for (j=1;j<Dx-1;j++) 
	{
	        g = (Mask[0][0]*list1[i-1][j-1]+Mask[0][1]*list1[i-1][j]
		    +Mask[0][2]*list1[i-1][j+1]+Mask[1][0]*list1[i][j-1]
		    +Mask[1][1]*list1[i][j]+Mask[1][2]*list1[i][j+1]
		    +Mask[2][0]*list1[i+1][j-1]+Mask[2][1]*list1[i+1][j]
		    +Mask[2][2]*list1[i+1][j+1])/Scale+Offset;  
		if (g>0xff) g=0xff;
		else if (g<0) g=0;
		list0[i][j] = (BYTE) g;   
	}
  }

list1是实际图像数据的缓冲区,list存放处理后的图像数据,Dx为图像宽度,Dy为图像高度。


以上即为基本的实现过程,在编码过程中,出现了一些问题,

1.关于指针做函数形参时, 函数的参数传递,本质上都是值传递,指针做形参时,形参的值实际为实参地址的拷贝,那么当需要修改参数地址时,那么可以用二维指针,即传入指针的指针来修改指针的地址。


2.关于封装成DLL的要点。

在.cpp中

#define DLL_API_SKY extern "C" _declspec(dllexport)

函数定义时,

void _stdcall ValueOfTwo(BITMAPINFO* lpbiIn,LPSTR lpbitsIn,BITMAPINFO** lpbiOut,LPSTR &lpbitsOut)

{

}

在.h中

#ifdef DLL_API_SKY
#else
#define DLL_API_SKY extern "C" _declspec(dllimport)
#endif

函数声明则是

DLL_API_SKY void _stdcall ValueOfTwo(BITMAPINFO* lpbiIn,LPSTR lpbitsIn,BITMAPINFO** lpbiOut,LPSTR &lpbitsOut);

在.def中

LIBRARY
EXPORTS
ValueOfTwo

这样导出的函数名就不会发生改变了。


3.在用SDI编写测试程序时,要在DOC中包含VIEW的头文件,那么就需要将VIEW.cpp里包含DOC的语句转移到VIEW.h里,


做完了8位BMP图像的一些处理,下一步再去做下24位的。

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