需配置好OpenCV和OCR环境下运行

1、opencv简介

OpenCV的全称是Open Source Computer Vision Library,是一个跨平台的计算机视觉库。

OpenCV用C++语言编写,它的主要接口也是C++语言,但是依然保留了大量的C语言接口。该库也有大量的Python, Java and MATLAB/OCTAVE (版本2.5)的接口。这些语言的API接口函数可以通过在线文档获得。现在也提供对于C#, Ruby的支持。

它有以下特点:

1) 开放的C/C++源码

2) 基于Intel处理器指令集开发的优化代码

3) 统一的结构和功能定义

4) 强大的图像和矩阵运算能力

5) 方便灵活的用户接口

6)同时支持MS-WINDOWS、Linux平台

作为一个基本的计算机视觉、图像处理和模式识别的开源项目,OPENCV可以直接应用于很多领域,作为第二次开发的理想工具。

2、OCR简介

OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程;即,对文本资料进行扫描,然后对图像文件进行分析处理,获取文字及版面信息的过程。如何除错或利用辅助信息提高识别正确率,是OCR最重要的课题,ICR(Intelligent Character Recognition)的名词也因此而产生。衡量一个OCR系统性能好坏的主要指标有:拒识率、误识率、识别速度、用户界面的友好性,产品的稳定性,易用性及可行性等。

3、OpenCV.jar包对图像实现灰度化原理

我们知道,在一个24位彩色图像中,每个像素由三个字节表示,通常表示为RGB。通常,许多24位彩色图像存储为32位图像,每个像素多余的字节存储为一个alpha值,表现有特殊影响的信息。在RGB模型中,如果R=G=B时,则彩色表示一种灰度颜色,其中R=G=B的值叫灰度值,因此,灰度图像每个像素只需一个字节存放灰度值(又称强度值、亮度值),灰度范围为0-255。这样就得到一幅图片的灰度图。

几种灰度化的方法

   ①、分量法

将彩色图像中的三分量的亮度作为三个灰度图像的灰度值,可根据应用需要选取一种灰度图像。

F1(i,j) = R(i,j) 
F2(i,j) = G(i,j) 
F3(i,j) = B(i,j)

代码示例:

import cv2.cv as cv

image = cv.LoadImage('mao.jpg')

b = cv.CreateImage(cv.GetSize(image), image.depth, 1)

g = cv.CloneImage(b)

r = cv.CloneImage(b)

cv.Split(image, b, g, r, None)

cv.ShowImage('a_window', r)

cv.WaitKey(0)

②、 最大值法

将彩色图像中的三分量亮度的最大值作为灰度图的灰度值。

F(i,j) = max(R(i,j), G(i,j), B(i,j))

代码示例:

image = cv.LoadImage('mao.jpg')

new = cv.CreateImage(cv.GetSize(image), image.depth, 1)for i in range(image.height):

for j in range(image.width):

new[i,j] = max(image[i,j][0], image[i,j][1], image[i,j][2])

cv.ShowImage('a_window', new)

cv.WaitKey(0)

③、平均值法

将彩色图像中的三分量亮度求平均得到一个灰度值。

F(i,j) = (R(i,j) + G(i,j) + B(i,j)) / 3

代码示例:

image = cv.LoadImage('mao.jpg')

new = cv.CreateImage(cv.GetSize(image), image.depth, 1)for i in range(image.height):

for j in range(image.width):

new[i,j] = (image[i,j][0] + image[i,j][1] + image[i,j][2])/3

cv.ShowImage('a_window', new)

cv.WaitKey(0)

④、加权平均法

根据重要性及其它指标,将三个分量以不同的权值进行加权平均。由于人眼对绿色的敏感最高,对蓝色敏感最低,因此,按下式对RGB三分量进行加权平均能得到较合理的灰度图像。

F(i,j) = 0.30R(i,j) + 0.59G(i,j) + 0.11B(i,j))

代码示例:

image = cv.LoadImage('mao.jpg')

new = cv.CreateImage(cv.GetSize(image), image.depth, 1)for i in range(image.height):

for j in range(image.width):

new[i,j] = 0.3 * image[i,j][0] + 0.59 * image[i,j][1] +  0.11 * image[i,j][2]

cv.ShowImage('a_window', new)

cv.WaitKey(0)

上面的公式可以看出绿色(G 分量)所占的比重比较大,所以有时候也会直接取G 分量进行灰度化。

代码示例:

image = cv.LoadImage('mao.jpg')

new = cv.CreateImage(cv.GetSize(image), image.depth, 1)for i in range(image.height):

for j in range(image.width):

new[i,j] = image[i,j][1]

cv.ShowImage('a_window', new)

cv.WaitKey(0)

而OpenCV的Java实现中采用的是加权法来实现图片的灰度化。

4、OpenCV.jar包对图像进行二值化处理原理

图像的二值化处理就是将图像上的点的灰度置为0或255,也就是将整个图像呈现出明显的黑白效果。即将256个亮度等级的灰度图像通过适当的阈值选取而获得仍然可以反映图像整体和局部特征的二值化图像。在数字图像处理中,二值图像占有非常重要的地位,特别是在实用的图像处理中,以二值图像处理实现而构成的系统是很多的,要进行二值图像的处理与分析,首先要把灰度图像二值化,得到二值化图像,这样子有利于在对图像做进一步处理时,图像的集合性质只与像素值为0或255的点的位置有关,不再涉及像素的多级值,使处理变得简单,而且数据的处理和压缩量小。为了得到理想的二值图像,一般采用封闭、连通的边界定义不交叠的区域。所有灰度大于或等于阈值的像素被判定为属于特定物体,其灰度值为255表示,否则这些像素点被排除在物体区域以外,灰度值为0,表示背景或者例外的物体区域。如果某特定物体在内部有均匀一致的灰度值,并且其处在一个具有其他等级灰度值的均匀背景下,使用阈值法就可以得到比较的分割效果。如果物体同背景的差别表现不在灰度值上(比如纹理不同),可以将这个差别特征转换为灰度的差别,然后利用阈值选取技术来分割该图像。动态调节阈值实现图像的二值化可动态观察其分割图像的具体结果。

5、OpenCV.jar包对图像进行腐蚀处理原理

对二值图腐蚀过程:

在下图中,左边是被处理的图象X(二值图象,我们针对的是黑点),中间是结构元素B,那个标有origin的点是中心点,即当前处理元素的位置。腐蚀的方法是,拿B的中心点和X上的点一个一个地对比,如果B上的所有点(指的是所有黑点)都在X的范围内(即X图上处理元素所在的位置以及它上,左两个点都是黑色),则该点保留,否则将该点去掉(变为白点);右边是腐蚀后的结果。可以看出,它仍在原来X的范围内,且比X包含的点要少,就像X被腐蚀掉了一层。

对灰度图像的腐蚀:

如下图,左边是要处理图像,中间是结构元素,右边是与对应每个像素的灰度值。

处理过程就是:与上面的B一样,中间是要处理的元素所在的位置,三个1所在的位置对应三个灰度值,然后将中间这个1对应的灰度值改成这三个最小的,如源图像第一个灰度值1,它上左都没有灰度值,所以最小就是它本身,所以输出也是1,再比如处理灰度值为22那个点的时候,上面是7左边是44,所以22应改为7。

6、OCR识别提取图片中文字原理

·  预处理:对包含文字的图像进行处理以便后续进行特征提取、学习。这个过程的主要目的是减少图像中的无用信息,以便方便后面的处理。在这个步骤通常有:灰度化(如果是彩色图像)、降噪、二值化、字符切分以及归一化这些子步骤。经过二值化后,图像只剩下两种颜色,即黑和白,其中一个是图像背景,另一个颜色就是要识别的文字了。降噪在这个阶段非常重要,降噪算法的好坏对特征提取的影响很大。字符切分则是将图像中的文字分割成单个文字——识别的时候是一个字一个字识别的。如果文字行有倾斜的话往往还要进行倾斜校正。归一化则是将单个的文字图像规整到同样的尺寸,在同一个规格下,才能应用统一的算法。

·  特征提取和降维:特征是用来识别文字的关键信息,每个不同的文字都能通过特征来和其他文字进行区分。对于数字和英文字母来说,这个特征提取是比较容易的,因为数字只有10个,英文字母只有52个,都是小字符集。对于汉字来说,特征提取比较困难,因为首先汉字是大字符集,国标中光是最常用的第一级汉字就有3755个;第二个汉字结构复杂,形近字多。在确定了使用何种特征后,视情况而定,还有可能要进行特征降维,这种情况就是如果特征的维数太高(特征一般用一个向量表示,维数即该向量的分量数),分类器的效率会受到很大的影响,为了提高识别速率,往往就要进行降维,这个过程也很重要,既要降低维数吧,又得使得减少维数后的特征向量还保留了足够的信息量(以区分不同的文字)。

· 分类器设计、训练和实际识别:分类器是用来进行识别的,就是对于第二步,对一个文字图像,提取出特征给,丢给分类器,分类器就对其进行分类,告诉你这个特征该识别成哪个文字。

·  后处理:后处理是用来对分类结果进行优化的,第一个,分类器的分类有时候不一定是完全正确的(实际上也做不到完全正确),比如对汉字的识别,由于汉字中形近字的存在,很容易将一个字识别成其形近字。后处理中可以去解决这个问题,比如通过语言模型来进行校正——如果分类器将“在哪里”识别成“存哪里”,通过语言模型会发现“存哪里”是错误的,然后进行校正。第二个,OCR的识别图像往往是有大量文字的,而且这些文字存在排版、字体大小等复杂情况,后处理中可以尝试去对识别结果进行格式化,比如按照图像中的排版排列什么的,举个栗子,一张图像,其左半部分的文字和右半部分的文字毫无关系,而在字符切分过程中,往往是按行切分的,那么识别结果中左半部分的第一行后面会跟着右半部分的第一行诸如此类。

代码:

TestOcr类:


[java]  view plain copy
  1. package com.njupt.yangmaohu;
  2. import java.io.File;
  3. import java.io.IOException;
  4. public class TestOcr {
  5. public static void main(String[] args) {
  6. // TODO 自动生成的方法存根
  7. //输入图片地址
  8. String path = "G:/ka.jpg";
  9. PictureManage pictureManage = new PictureManage(path); //对图片进行处理
  10. pictureManage.imshow();
  11. try {
  12. String valCode = new OCR().recognizeText(new File("xintu.jpg"), "jpg");//jpg是图片格式
  13. System.out.println("图片中文字为:"+"\n"+valCode);
  14. } catch (IOException e) {
  15. e.printStackTrace();
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }

PictureManage类:

[java]  view plain copy
  1. package com.njupt.yangmaohu;
  2. import java.awt.Graphics;
  3. import java.awt.Image;
  4. import java.awt.image.BufferedImage;
  5. import java.awt.image.DataBufferByte;
  6. import java.io.File;
  7. import java.io.IOException;
  8. import javax.imageio.ImageIO;
  9. import org.opencv.core.Core;
  10. import org.opencv.core.CvType;
  11. import org.opencv.core.Mat;
  12. import org.opencv.core.Size;
  13. import org.opencv.highgui.Highgui;
  14. import org.opencv.imgproc.Imgproc;
  15. public class PictureManage {
  16. private Mat image;
  17. //private JLabel jLabelImage;
  18. public PictureManage(String fileName) {
  19. System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
  20. this.image= Highgui.imread(fileName);
  21. }
  22. /**
  23. * 图片画质处理
  24. * @param image
  25. * @return
  26. */
  27. public static Mat setMatImage(Mat image) {
  28. Mat loadeMatImage = new Mat();
  29. //灰度处理
  30. Imgproc.cvtColor(image,image,Imgproc.COLOR_RGB2GRAY);
  31. //二值化处理
  32. Mat binaryMat = new Mat(image.height(), image.width(), CvType.CV_8UC1);
  33. Imgproc.threshold(image, binaryMat,20, 300, Imgproc.THRESH_BINARY);
  34. //图像腐蚀
  35. Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT,
  36. new Size(500,500));
  37. Imgproc.erode(binaryMat, image,element);
  38. //loadeMatImage = image;
  39. loadeMatImage = binaryMat;
  40. return loadeMatImage;
  41. }
  42. /**
  43. * Mat转image
  44. * @param matrix
  45. * @return
  46. */
  47. private Image toBufferedImage(Mat matrix) {
  48. int type = BufferedImage.TYPE_BYTE_GRAY;
  49. if (matrix.channels()>1) {
  50. type = BufferedImage.TYPE_3BYTE_BGR;
  51. }
  52. int bufferSize = matrix.channels()*matrix.cols()*matrix.rows();
  53. byte[] buffer = new byte[bufferSize];
  54. matrix.get(0, 0, buffer);
  55. BufferedImage image = new BufferedImage(matrix.cols(), matrix.rows(),type);
  56. final byte[] targetPxiels = ((DataBufferByte)image.getRaster().getDataBuffer()).getData();
  57. System.arraycopy(buffer, 0, targetPxiels, 0, buffer.length);
  58. return image;
  59. }
  60. /***
  61. * 将Image变量保存成图片
  62. * @param im
  63. * @param fileName
  64. */
  65. public  void  saveImage(Image im ,String  fileName) {
  66. int w = im.getWidth(null);
  67. int h = im.getHeight(null);
  68. BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
  69. Graphics g = bi.getGraphics();
  70. g.drawImage(im, 0, 0, null);
  71. try {
  72. ImageIO.write(bi, "jpg", new File(fileName));
  73. } catch (IOException e) {
  74. e.printStackTrace();
  75. }
  76. }
  77. /**
  78. * 图片处理
  79. * @param args
  80. */
  81. public void  imshow(){
  82. //添加原图
  83. Image originalImage = toBufferedImage(image);
  84. saveImage(originalImage, "yuantu.jpg");
  85. //jLabelImage.setIcon(new ImageIcon(originalImage));
  86. //添加处理图
  87. Mat mat1 = setMatImage(image);
  88. Image newImage = toBufferedImage(mat1);
  89. saveImage(newImage, "xintu.jpg");
  90. }
  91. }



ImageIOHelper类:

[java]  view plain copy
  1. package com.njupt.yangmaohu;
  2. import java.awt.image.BufferedImage;
  3. import java.io.File;
  4. import java.io.IOException;
  5. import java.util.Iterator;
  6. import java.util.Locale;
  7. import javax.imageio.IIOImage;
  8. import javax.imageio.ImageIO;
  9. import javax.imageio.ImageReader;
  10. import javax.imageio.ImageWriteParam;
  11. import javax.imageio.ImageWriter;
  12. import javax.imageio.metadata.IIOMetadata;
  13. import javax.imageio.stream.ImageInputStream;
  14. import javax.imageio.stream.ImageOutputStream;
  15. import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam;
  16. public class ImageIOHelper {
  17. /**
  18. * 图片文件转换为tif格式
  19. * @param imageFile 文件路径
  20. * @param imageFormat 文件扩展名
  21. * @return
  22. */
  23. public static File createImage(File imageFile, String imageFormat) {
  24. File tempFile = null;
  25. try {
  26. Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(imageFormat);
  27. ImageReader reader = readers.next();
  28. ImageInputStream iis = ImageIO.createImageInputStream(imageFile);
  29. reader.setInput(iis);
  30. //Read the stream metadata
  31. IIOMetadata streamMetadata = reader.getStreamMetadata();
  32. //Set up the writeParam
  33. TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.CHINESE);
  34. tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
  35. //Get tif writer and set output to file
  36. Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("tiff");
  37. ImageWriter writer = writers.next();
  38. BufferedImage bi = reader.read(0);
  39. IIOImage image = new IIOImage(bi,null,reader.getImageMetadata(0));
  40. tempFile = tempImageFile(imageFile);
  41. ImageOutputStream ios = ImageIO.createImageOutputStream(tempFile);
  42. writer.setOutput(ios);
  43. writer.write(streamMetadata, image, tiffWriteParam);
  44. ios.close();
  45. writer.dispose();
  46. reader.dispose();
  47. } catch (IOException e) {
  48. e.printStackTrace();
  49. }
  50. return tempFile;
  51. }
  52. private static File tempImageFile(File imageFile) {
  53. String path = imageFile.getPath();
  54. StringBuffer strB = new StringBuffer(path);
  55. strB.insert(path.lastIndexOf('.'),0);
  56. return new File(strB.toString().replaceFirst("(?<=//.)(//w+)$", "tif"));
  57. }
  58. }


OCR类:

[java]  view plain copy
  1. package com.njupt.yangmaohu;
  2. import java.io.BufferedReader;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.InputStreamReader;
  6. import java.util.ArrayList;
  7. import java.util.List;
  8. import org.jdesktop.swingx.util.OS;
  9. public class OCR {
  10. private final String LANG_OPTION = "-l";  //英文字母小写l,并非数字1
  11. private final String EOL = System.getProperty("line.separator");
  12. private String tessPath = "C://Program Files (x86)//Tesseract-OCR";//tesseract-ocr安装地址
  13. //private String tessPath = new File("tesseract").getAbsolutePath();
  14. public String recognizeText(File imageFile,String imageFormat)throws Exception{
  15. File tempImage = ImageIOHelper.createImage(imageFile,imageFormat);
  16. File outputFile = new File(imageFile.getParentFile(),"output");
  17. StringBuffer strB = new StringBuffer();
  18. List<String> cmd = new ArrayList<String>();
  19. if(OS.isWindowsXP()){
  20. cmd.add(tessPath+"//tesseract");
  21. }else if(OS.isLinux()){
  22. cmd.add("tesseract");
  23. }else{
  24. cmd.add(tessPath+"//tesseract");
  25. }
  26. cmd.add("");
  27. cmd.add(outputFile.getName());
  28. //cmd.add(LANG_OPTION);
  29. //cmd.add("chi_sim");
  30. //cmd.add("eng");
  31. ProcessBuilder pb = new ProcessBuilder();
  32. pb.directory(imageFile.getParentFile());
  33. cmd.set(1, tempImage.getName());
  34. pb.command(cmd);
  35. pb.redirectErrorStream(true);
  36. Process process = pb.start();
  37. //tesseract.exe 1.jpg 1 -l chi_sim
  38. int w = process.waitFor();
  39. //删除临时正在工作文件
  40. tempImage.delete();
  41. if(w==0){
  42. BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath()+".txt"),"UTF-8"));
  43. String str;
  44. while((str = in.readLine())!=null){
  45. strB.append(str).append(EOL);
  46. }
  47. in.close();
  48. }else{
  49. String msg;
  50. switch(w){
  51. case 1:
  52. msg = "Errors accessing files.There may be spaces in your image's filename.";
  53. break;
  54. case 29:
  55. msg = "Cannot recongnize the image or its selected region.";
  56. break;
  57. case 31:
  58. msg = "Unsupported image format.";
  59. break;
  60. default:
  61. msg = "Errors occurred.";
  62. }
  63. tempImage.delete();
  64. //throw new RuntimeException(msg);
  65. }
  66. new File(outputFile.getAbsolutePath()+".txt").delete();
  67. return strB.toString();
  68. }
  69. }

OpenCV+OCR文字识别相关推荐

  1. opencv ocr文字识别_用OpenCV和OCR识别图片中的表格数据

    ♚ 作者: jclian,喜欢算法,热爱分享,希望能结交更多志同道合的朋友,一起在学习Python的道路上走得更远!   在很多时候,我们的数据来源形式是多种多样的,有时候数据(或表格)也会呈现在图片 ...

  2. opencv学习笔记五--文件扫描+OCR文字识别

    opencv学习笔记五--文件扫描+OCR文字识别 文件扫描 定义函数 边缘检测 获取轮廓 变换 OCR文字识别 环境配置 代码 文件扫描 # 导入工具包 import numpy as np imp ...

  3. Windows下使用Tesseract进行OCR文字识别

    Windows下使用Tesseract进行OCR文字识别 Tesseract最初由惠普实验室支持,用于电子版文字识别,1996年被移植到Windows上,1998年进行了C++化,在2005年Tess ...

  4. Tesseract Ocr文字识别实战(新版本,扩展手写文字识别)

    目录 1.Tesseract Ocr文字识别 1.1 运行环境 1.2 python模块 1.3 配置tesseract运行文件 1.4 代码识别 2. 手写汉字识别 2.1 下载库 2.2 代码 1 ...

  5. OCR文字识别笔记总结

    OCR的全称是Optical Character Recognition,光学字符识别技术.目前应用于各个领域方向,甚至这些应用就在我们的身边,比如身份证的识别,交通路牌的识别,车牌的自动识别等等.本 ...

  6. 用paddleocr识别汉字_使用飞桨一步步实现多语言OCR文字识别软件

    目录 急速版: 做了一个OCR文字识别工具. 好了,看到这里就行了,使用方法上面链接里有. ----------------------------------------- 如果您是普通用户,可以直 ...

  7. 人工智能OCR文字识别研究

    1 研究背景 人工智能是研究开发能够模拟.延伸和扩展人类智能的理论.方法.技术及应用系统的一门新的技术科学,研究目的是促使智能机器会听(语音识别.机器翻译等).会看(图像识别.文字识别等).会说(语音 ...

  8. python(自动化)利用selenium+百度ocr文字识别验证码实现自动登陆登陆CET-四级报名系统

    操作步骤: 1:登陆打开CET-考试系统 2:填写相关登陆信息 3:调用百度ocr实现文字验证码识别 4:实现登陆 如何使用和调用百度ocr文字识别接口 1:进入百度AI开发平台:链接 2:在页面上选 ...

  9. Python制作简易OCR文字识别系统

    前不久看了一篇"如何使用Python检测和识别车牌?"用OpenCV对输入图像进行预处理,用imutils将原始输入图像裁剪成所需的大小,用pytesseract将提取车牌字符转换 ...

最新文章

  1. 新手搭建阿里云FTP服务器
  2. 第十六届全国大学生智能汽车竞赛(西部赛区) 竞速组成绩及获奖情况公示
  3. 9076什么意思_9076西南大学人力资源开发与管理答案
  4. 单个像素 亮度 HTML,YUV与像素值之间的关系
  5. iOS开发(8)UISwitch
  6. 压力测试和负载测试区别_如何理解与区分软件性能测试、负载测试、稳定性测试、压力测试...
  7. 超强1000个jquery极品插件!
  8. 3DS MAX的灯光
  9. Hololens开发常见错误
  10. Java基础——学生管理系统
  11. Redis Setex
  12. web前端面试题之肉(css)
  13. excel绁炵粡缃戠粶瀹炵幇,excel 绁炵粡缃戠粶
  14. 2022年厦门大学计算机考研复试分数线多少
  15. IT界十年最重要10家公司排行榜
  16. 如果太阳突然爆炸 地球会发生什么?| 1分钟了解广义相对论
  17. python爬虫之爬取起点中文网小说
  18. HTML5期末大作业:旅游网页设计与实现——旅游风景区网站HTML+CSS+JavaScript 景点静态网页设计 学生DW静态网页设计...
  19. 计算机AMD方案不超过4000元,4000元左右AMD R5-1400配RX570全新芯片显卡电脑配置推荐...
  20. 2021云栖大会:阿里云自研技术大爆发,连发多款重磅产品

热门文章

  1. HTTP请求和请求头的详解
  2. latex小技巧—极限符号下方分成两行
  3. MOGRT动态图标模板 爱情元素手绘爱心特效pr视频模板
  4. 删除计算机硬盘中的文件,硬盘上数据如何彻底删除 硬盘上数据彻底删除方法【详解】...
  5. [高数][高昆轮][高等数学上][第一章-函数与极限]03.函数的极限
  6. windows自定义屏幕大小,分辨率大小,自定义电脑屏幕分辨率
  7. (一) windows 10 下安装 mongodb 并 globalsign/mgo 包的简单使用
  8. [HNOI2007]最小矩形覆盖(旋转卡壳)
  9. 插秧诗 - 退步原来是向前
  10. 修改图片文件后缀,将JPG转换为PNG