(三)将PaddleOCR编译成dll通过Java调用实现ocr识别

news2024/11/28 14:54:24

说明: 本文编译的PaddleOCR版本:v2.8.1,关于windows下如何生成c++项目及如何编译PaddleOCR请参照我的上一篇文章《(二)Windows通过vs c++编译PaddleOCR-2.8.1-CSDN博客》,本文是上一个篇文章的延伸。

  背景:

        目前很多项目后端都是java EE编写,部署于linux和windows下,PaddleOCR通过hubServing很容易部署到linux下,但是因为使用python及一次http请求中转,速度很不理想,故而需要采用原始jni调用c++动态库的模式来部署ocr服务。

  环境准备

        参照《(二)Windows通过vs c++编译PaddleOCR-2.8.1-CSDN博客》生成vs 2022的c++项目。

        书接上文,在d:\ppocr_cpp文件夹下新增一个jni文件夹存放jni头文件,再新增一个ocr_release文件夹存放编译后的javaclass及dll等文件,目录结构如下图:

一、JNI的编写

        为了便于大家好测试及降低学习成本,我的java代码编写就使用文本文档编写。

        我的本地java环境是jdk_1.8.0_411,因为JNI是很早就使用的成熟技术,所以目前现存的所有版本jdk都通过,大家使用自己顺手的jdk即可。

        1、编写java jni代码

        编写java jni功能,包含:加载动态库dll,初始化PPOCR,执行ocr识别。

        在d:\PaddleOCR\ocr_release文件夹下新增PaddleOcrJni.java,注意文件编码是UTF-8,推荐使用vs code来编写,内容如下:

import java.io.File;
import java.io.IOException; 
import java.util.Calendar;

/*
 * @author      : wuxiutong
 * @date        : 2024/10/14 14:38
 * @description : PaddleOcrJni头文件
 */
public class PaddleOcrJni {

    // 类加载的同时加载dll动态库,该代码也可以写入到具体方法中,只要调用前加载就可以,此处路径为了演示方便直接写死绝对路径,
    static {
        System.load("D:/ppocr_cpp/ocr_release/ppocr_jni.dll");
    }

    /***
     *  jni接口:传递文件完整路径,返回识别结果字符串。
     * @param imgFullPath 图片文件完整路径
     * @return 返回ocr识别后的json字符串大致格式如下{"status":"状态码","msg":"错误信息","results":[]} },status值000则是成功,否则失败
     */
    public native String ocr(String[] imgFullPath);

    /***
     * jni接口:初始化ocr
     * @param modelVer 模型版本,只v1、v2、v3、v4目前用最新的v4
     * @param useGPU 是否使用GPU,1使用gpu,否则使用cpu
     * @param useCls  是否使用方向识别类,1使用,否则不使用
     * @param isTable 是否识别表格,1使用,否则不使用,返回表格结构
     * @param isLayout 是否版面分析,1使用,否则不使用,通常和isTable配合使用
     * @return 返回结果字符串{"status":"000","msg":"错误信息"},status值000则是成功,否则失败
     */
    public native String init(int modelVer,int useGPU,int useCls,int isTable,int isLayout);


    // main函数,此处用作具体演示,用到项目中则需要根据业务做具体调整。
    public static void main(String[] args) {
        try{
        Calendar calendar = Calendar.getInstance();
        // 执行初始化操作
        PaddleOcrJni paddleOcrJni = new PaddleOcrJni();
        // 执行PPOCR初始化
        paddleOcrJni.init(4,0,1,0,0);
        System.out.println("ocr初始化完成!"); 
        // 将需要ocr识别的图片添加值待识别列表
        String[] filesArray = new String[1]; 
        filesArray[0] = new File("D:/ppocr_cpp/ocr_release/pic/jzpz.png").getCanonicalPath();  
        String ocrResult = paddleOcrJni.ocr(filesArray);
        System.out.println("图片识别结果");
        System.out.println(ocrResult);
        System.out.println("耗时:" + (Calendar.getInstance().getTimeInMillis() - calendar.getTimeInMillis()) + "ms");
        }catch(Exception e1){ 
            System.out.println("执行失败,错误信息:"+e1.getMessage());
        }
    }
}

2、编译jni头文件

        打开命令行,进入到d:\PaddleOCR\ocr_release文件夹下执行一下代码生成h头文件。

javac -encoding UTF-8  -h  PaddleOcrJni.java

        生成头文件,如下图所示。

3、添加头文件到vs项目中

        将上一步生成的PaddleOcrJni.h文件复制到d:\PaddleOCR\jni文件夹下,再将jdk下的include文件夹中所有内容如下图所示的包含win32文件及所有h头文件,复制到到d:\PaddleOCR\jni下。

         复制后的d:\PaddleOCR\jni文件夹内容如下图所示:

使用vs 2022 打开D:\ppocr_cpp\PaddleOCR\deploy\cpp_infer\build\ppocr.sln,如下图所示:

         在打开vs 2022界面中右侧“解决方案资源管理器”中的的ppocr项目上右键,然后点击“属性”,在弹出的“ppocr属性页”弹窗中,左侧“配置属性”—> “C/C++” —>  “常规” 中修改“附加包含目录”中点击“编辑”在弹出的“附件包含目录”弹窗中新增两行(千万不要删除已经存在的其他目录)分别指向“d:\ppocr_cpp\jni”和“d:\ppocr_cpp\jni\win32”,如以下图所示,最后确定并且应用。

        以上操作的目的是让vs c++编译器能正确的检测到我们的java标准的头文件及上一步生成的PaddleOcrJni头文件,若后续新增c++编译时报错或者vs 2022别c++时如JNIEXPORT飘红等就需要检查此处是否设置正确。

二、修改vs项目为编译dll模式

        在打开vs 2022的界面中右侧“解决方案资源管理器”中的的ppocr项目上右键,然后点击“属性”,在弹出的“ppocr属性页”弹窗中选择“配置属性”—>“常规”—>“配置类型”修改为动态库dll,文件名调整为ppocr_jni(可以任君调整,我这这是方便演示所有使用ppocr_jni),如下图所示:

        在选择“配置属性”—>“高级”—>“配置类型”,将表格中的“目标文件扩展名”修改成.dll,如下图所示,最后应用即可。

 三、编写c++中的jni实现

1、编写jni.cpp文件

        在ppocr项目下的源代码文件夹中新增jni.cpp文件。

        以下是jni.cpp文件的全部内容:

#include "opencv2/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#include <vector>

#include <time.h>
#include <include/args.h>
#include <include/paddleocr.h>
#include <include/paddlestructure.h>
#include <PaddleOcrJni.h>

#include <fstream>
#include <iostream>
#include <string> 
#include <exception> 

using namespace PaddleOCR;
PPOCR ocr;

// modelVer v1\v2\v3\v4
JNIEXPORT jstring JNICALL Java_PaddleOcrJni_init
(JNIEnv* env, jobject, jint modelVer, jint useGPU, jint useCls, jint isTable, jint isLayout) {
	try {
		FLAGS_det_model_dir = "./infer/ch_PP-OCRv4_det_infer";
		FLAGS_rec_model_dir = "./infer/ch_PP-OCRv4_rec_infer";
		FLAGS_cls_model_dir = "./infer/ch_ppocr_mobile_v2.0_cls_infer";
		FLAGS_rec_char_dict_path = "./utils/ppocr_keys_v1.txt";
		FLAGS_layout_dict_path = "./utils/dict/layout_dict/layout_publaynet_dict.txt";
		FLAGS_table_char_dict_path = "./utils/dict/table_structure_dict_ch.txt";
		if (useCls == 1) {
			FLAGS_cls = true;
		}
		// 执行初始化操作
		ocr.init();
		std::ostringstream rstOSS;
		rstOSS << "{\"msg\":\"\",\"status\" : \"000\"}";
		std::string ocrRstStr = rstOSS.str();
		jstring rr = env->NewStringUTF(ocrRstStr.c_str());
		return rr;
	}
	catch (const std::exception& e) {
		// 捕获所有标准类异常
		std::ostringstream rstOSS;
		rstOSS << "{\"msg\":\"ocr初始化失败,失败详情:";
		rstOSS << e.what();
		rstOSS << "\",\"status\" : \"001\"}";
		std::string ocrRstStr = rstOSS.str();
		jstring rr = env->NewStringUTF(ocrRstStr.c_str());
		return rr;
	}
	catch (...) {
		// 捕获所有非标准类异常(自定义)
		std::ostringstream rstOSS;
		rstOSS << "{\"msg\":\"ocr初始化失败,未知异常\",\"status\" : \"001\"}";
		std::string ocrRstStr = rstOSS.str();
		jstring rr = env->NewStringUTF(ocrRstStr.c_str());
		return rr;
	}
}


/**
jni传入
*/
JNIEXPORT jstring JNICALL Java_PaddleOcrJni_ocr(JNIEnv* env, jobject, jobjectArray stringArray) {
	// 获取数组长度  
	jsize length = env->GetArrayLength(stringArray);
	std::cerr << "------recived image path count: " << length << "------" << std::endl;
	std::vector<cv::Mat> img_list;
	// 创建一个C字符串数组来存储转换后的字符串  
	std::vector<char*> cStrings(length);
	try {
		for (jsize i = 0; i < length; ++i) {
			// 获取Java字符串对象  
			jstring javaString = (jstring)env->GetObjectArrayElement(stringArray, i);

			// 检查是否获取到有效的Java字符串对象  
			if (javaString == nullptr) {
				throw std::runtime_error("------Failed to get Java string element------");
			}

			// 将Java字符串转换为C字符串  
			const char* cString = env->GetStringUTFChars(javaString, nullptr);

			// 存储C字符串的指针  
			cStrings[i] = strdup(cString); // 注意:strdup分配了内存,需要后续释放  

			// 释放JNI分配的字符串内存  
			env->ReleaseStringUTFChars(javaString, cString);

			// 释放Java字符串对象的本地引用(避免本地引用泄漏)  
			env->DeleteLocalRef(javaString);
		}

		// 在这里处理cStrings数组中的C字符串  
		for (jsize i = 0; i < length; ++i) {
			cv::Mat mat = cv::imread(cStrings[i], cv::IMREAD_COLOR);
			img_list.push_back(mat);
		}
		// 释放C字符串数组中的内存  
		for (jsize i = 0; i < length; ++i) {
			free(cStrings[i]);
		}
	}
	catch (const std::exception& e) {
		// 处理异常,例如打印错误信息  
		std::cerr << "------Error processing string array: " << e.what() << "------" << std::endl;

		// 释放已分配的C字符串内存(如果有的话)  
		for (jsize i = 0; i < length; ++i) {
			if (cStrings[i] != nullptr) {
				free(cStrings[i]);
			}
		}
	}

	try {
		if (img_list.size() <= 0) {
			std::ostringstream rstOSS;
			rstOSS << "{\"msg\":\"未提供文件列表\", \"results\":[],\"status\" : \"001\"}";
			std::string ocrRstStr = rstOSS.str();
			jstring rr = env->NewStringUTF(ocrRstStr.c_str());
			return rr;
		}
		std::cerr << "------ocr images count: " << img_list.size() << "------" << std::endl;

		std::vector<std::vector<OCRPredictResult>> ocr_results = ocr.ocr(img_list, true, true, false);
		// 将识别对象转换为json字符串
		std::string ocrRstStr = "";
		//{"msg":"","results":[[],[],[]],"status":"000"}

		std::ostringstream resultsOSS;
		for (int rstCount = 0;rstCount < ocr_results.size();rstCount++) {
			for (int i = 0;i < ocr_results[rstCount].size();i++) {
				std::ostringstream singleRstOss;
				OCRPredictResult rst = ocr_results[rstCount][i];
				singleRstOss << "{";
				// 预测可信任值  
				singleRstOss << "\"confidence\":";
				singleRstOss << rst.score;
				// 识别字符串
				singleRstOss << ",";
				singleRstOss << "\"text\":";
				singleRstOss << ("\"" + rst.text + "\"");
				singleRstOss << (",");
				// 定位区域
				singleRstOss << ("\"text_region\":[");
				std::string boxStr = "";
				std::vector<std::vector<int>> boxes = rst.box;
				for (int n = 0; n < boxes.size(); n++) {
					singleRstOss << ("[");
					singleRstOss << boxes[n][0];
					singleRstOss << ",";
					singleRstOss << boxes[n][1];
					singleRstOss << ("]");
					if (n != rst.box.size() - 1) {
						singleRstOss << (",");
					}
				}
				singleRstOss << (boxStr.c_str());
				singleRstOss << ("]");
				singleRstOss << ("}");
				if (i != 0) {
					resultsOSS << ",";
				}
				resultsOSS << "[";
				resultsOSS << singleRstOss.str();
				resultsOSS << "]";
			}
		}
		// 拼接最后结果
		ocrRstStr = "{\"msg\":\"\",\"results\":[" + resultsOSS.str() + "],\"status\":\"000\"}";
		jstring rr = env->NewStringUTF(ocrRstStr.c_str());
		return rr;
	}
	catch (std::exception& e) {
		std::cout << "------ocr exception:" << e.what() << ",detail:" << std::endl;
		std::string err = "";
		std::ostringstream singleRstOss;
		singleRstOss << e.what();
		err.append("{\"msg\":\"");
		err.append(singleRstOss.str());
		err.append("\",\"results\":[],\"status\":\"err\"}");
		jstring rr = env->NewStringUTF(err.c_str());
		return rr;
	}
}

2、修改paddleocr.cpp文件

        修改ppocr项目下的“外部依赖项”paddleocr.h文件中新增一个成员函数init()成员函数,添加后的结果如下图:

        修改ppocr项目下的“源文件”下的paddleocrc.cpp文件,新增init()方法,将原PPOCR()构造函数中的内容添加init()方法中,清空PPOCR()构造函数中的内容,调整后的代码如下图: 

 四、编译ppocr

        1、编译ppocr_jni.dll文件

           完成代码编写后就可以点击“生成”—>“重新生成解决方案”,如果系统不报错即可在D:\ppocr_cpp\PaddleOCR\deploy\cpp_infer\build\Release下看到已经生成ppcor_jni.dll文件,结果如下所示:

2、复制生成的ppocr_jni.dll等文件

        复制上一步生成的Release文件夹下的所有dll文件到D:\ppocr_cpp\ocr_release文件夹中,复制完成后如下图所示:

3、复制D:\ppocr_cpp\PaddleOCR\ppocr\utils文件夹

        将D:\ppocr_cpp\PaddleOCR\ppocr下的utils文件夹整个复制到D:\ppocr_cpp\ocr_release文件夹中,复制后D:\ppocr_cpp\ocr_release\utils\文件夹下将包含ocr需要使用的到ppocr_keys_v1.txt字典等文件,结果如下图所示:

4、复制D:\ppocr_cpp\infer文件夹

        将D:\ppocr_cpp\infer文件夹整个复制到D:\ppocr_cpp\ocr_release下,包含了ocr需要使用到的3个模型,复制后的截图如下图:

 五、运行查看效果

        运行前检查d:\ppocr_cpp\ocr_release目录文件信息是否完整,完整文件如下:

        打开命令行,进入到d:\ppocr_cpp\ocr_release目录下,运行以下脚本:

java PaddleOcrJni

如下图: 

 效果:

        因为java代码中指定了待识别ocr图片在D:/ppocr_cpp/ocr_release/pic/jzpz.png,所以识别结果返回err。

        添加一张有文字的png图片到D:/ppocr_cpp/ocr_release/pic下并命名为jzpz.png再次执行代码即可查看到最终效果:

 

        至此将PaddleOCR编译成dll动态库,供java调用实现java的ocr识别就到结束,以上代码为本人编写的demo,具体的可在此基础上按业务需求做改造。 

后话:

        上面代码中可以发现我jni接口中有一个单独的init方法,并且修改了paddleocr.h和paddleocr.cpp中的默认构造函数,我的目的有二:

        1、解决官方代码样例(main.cpp)中的PPOCR每次初始化对象的时候就加载ocr模型及字典等信息(args.cpp文件中定义,ppocr.exe运行时给参),我需要的是固化和动态通过jni传递这些参数,但是我又不愿意去修改args.cpp文件,因为说不定官方在后续某个版本中增加参数会修改该文件,所以我调整了官方的paddleocr的构造函数,新增一个init函数来由我jni调用它来初始化PPOCR,尽量降低对官方代码的修改,方便后续升级。

        2、解决java每次调用ocr动态库做ocr识别时都会重新初始化PPOCR(初始化会耗时),所有我的java代码在使用时会修改成单例模式,只有第一次识别ocr的时候完成PPOCR的初始化,后续的ocr识别就不用再初始化,从而提高调用OCR所耗费的时间。

后续文章应该会写关于PaddleOCR识别的模型训练教程,因为目前这个版本(v4)的模型库有些汉字是无法识别的,如蒯字(大家可以试试),所以实际使用中需要自己训练模型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2222411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

douyin uid转sec_uid 各种进行转换

第一步输入uid&#xff1a; 进行转换&#xff1a; 同时支持接口转换&#xff0c;批量转换&#xff0c;是一个很实用的工具 uid转sec_uid

微信小程序上传图片添加水印

微信小程序使用wx.chooseMedia拍摄或从手机相册中选择图片并添加水印&#xff0c; 代码如下&#xff1a; // WXML代码&#xff1a;<canvas canvas-id"watermarkCanvas" style"width: {{canvasWidth}}px; height: {{canvasHeight}}px;"></canvas&…

如何使用 Spring Cloud 实现客户端负载平衡

微服务系统通常运行每个服务的多个实例。这是实施弹性所必需的。因此&#xff0c;在这些实例之间分配负载非常重要。执行此操作的组件是负载均衡器。Spring 提供了一个 Spring Cloud Load Balancer 库。在本文中&#xff0c;您将学习如何使用它在 Spring Boot 项目中实现客户端…

QPainterPath路径类

函数drawPath()绘制的是一个复合的图形&#xff0c;它使用一个QPainterPath类型的参数作为绘图的对象,QPainterPath类用于记录绘图的操作顺序&#xff0c;优点是绘制复杂图形时只需要创建一个painterpath,然后重复调用就可以了 在使用QPainterPath把路径画好之后&#xff0c;我…

脚本-把B站缓存m4s文件转换成mp4格式

js脚本&#xff0c;自动处理视频 1. 需求简介1.1 pc安装b站客户端1.2 设置视频缓存目录1.3 找个视频缓存1.4 打开缓存文件夹![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/0eb346a84d5f42a7908f1d39bf410c3b.png)1.5 用notepad编辑后缀m4s文件&#xff0c;删除文件内…

Windows系统启动MongoDB报错无法连接服务器

文章目录 发现问题解决办法 发现问题 1&#xff09;、先是发现执行 mongo 命令&#xff0c;启动报错&#xff1a; error: MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017&#xff1b; 2&#xff09;、再检查 MongoDB 进程 tasklist | findstr mongo 发现没有进程&a…

澳元/美元价格预测:不排除跌至0.6600的可能

澳元/美元一路下跌至0.6620附近。美元保持强劲上涨势头&#xff0c;升至创下三个月新高。汇价的下跌让关键的200日均线受到考验。 澳元/美元周三再度遭遇抛售兴趣&#xff0c;迅速扭转周二的多头尝试&#xff0c;滑落至0.6630附近的新低。这次急剧下跌也对关键的200日均线构成…

yjs机器学习常见算法01——KNN(02)Kd树

1.什么是Kd树&#xff0c;为什么要引入Kd树 knn是寻找k个邻近的点&#xff0c;在这个过程中&#xff0c;需要一个点一个点的与未分类点进行比较&#xff0c;这样的时间复杂度非常高&#xff0c;因此引入了一种原理类似二叉树的Kd树&#xff0c;以减少比较搜索的次数。 kd树的本…

PyTorch求导相关

PyTorch是动态图&#xff0c;即计算图的搭建和运算是同时的&#xff0c;随时可以输出结果&#xff1b;而TensorFlow是静态图。 在pytorch的计算图里只有两种元素&#xff1a;数据&#xff08;tensor&#xff09;和 运算&#xff08;operation&#xff09; 运算包括了&#xf…

Psychophysiology:脑-心交互如何影响个体的情绪体验?

摘要 情绪的主观体验与对身体(例如心脏)活动变化的情境感知和评估相关。情绪唤醒增加与高频心率变异性(HF-HRV)降低、EEG顶枕区α功率降低以及心跳诱发电位(HEP)振幅较高有关。本研究使用沉浸式虚拟现实(VR)技术来研究与情绪唤醒相关的脑心相互作用&#xff0c;以实现自然而可…

SSM考研科目学习APP-计算机毕业设计源码90377

摘 要 基于Android的考研科目学习系统的设计与实现&#xff0c;旨在为广大考研学子提供一个便捷、高效的学习平台。该系统充分利用Android操作系统的广泛普及与灵活定制性&#xff0c;结合考研科目的特点和需求&#xff0c;实现了个性化的学习方案、丰富的题库资源以及智能化…

【个人同步与备份】电脑(Windows)与手机/平板(Android)之间文件同步

文章目录 1. syncthing软件下载2. syncthing的使用2.1. 添加设备2.1.1. syncthing具备设备发现功能&#xff0c;因此安装好软件&#xff0c;只需确认设备信息是否对应即可2.1.2. 如果没有发现到&#xff0c;可以通过设备ID连接2.1.3. 设置GUI身份验证用户&#xff0c;让无关设备…

LeetCode: 3274. 检查棋盘方格颜色是否相同

一、题目 给你两个字符串 coordinate1 和 coordinate2&#xff0c;代表 8 x 8 国际象棋棋盘上的两个方格的坐标。   以下是棋盘的参考图。   如果这两个方格颜色相同&#xff0c;返回 true&#xff0c;否则返回 false。   坐标总是表示有效的棋盘方格。坐标的格式总是先…

大模型技术学习过程梳理,零基础入门到精通,收藏这一篇就够了

“ 学习是一个从围观到宏观&#xff0c;从宏观到微观的一个过程 ” 今天整体梳理一下大模型技术的框架&#xff0c;争取从大模型所涉及的理论&#xff0c;技术&#xff0c;应用等多个方面对大模型进行梳理。 01 — 大模型技术梳理 这次梳理大模型不仅仅是大模型本身的技术…

接口测试(八)jmeter——参数化(CSV Data Set Config)

一、CSV Data Set Config 需求&#xff1a;批量注册5个用户&#xff0c;从CSV文件导入用户数据 1. 【线程组】–>【添加】–>【配置元件】–>【CSV Data Set Config】 2. 【CSV数据文件设置】设置如下 3. 设置线程数为5 4. 运行后查看响应结果

vue3项目页面实现echarts图表渐变色的动态配置

完整代码可点击vue3项目页面实现echarts图表渐变色的动态配置-星林社区 https://www.jl1mall.com/forum/PostDetail?postId202410151031000091552查看 一、背景 在开发可配置业务平台时&#xff0c;需要实现让用户对项目内echarts图表的动态配置&#xff0c;让用户脱离代码也…

基于Matlab 人脸识别技术

Matlab 人脸识别技术 算法流程&#xff1a; 本系统运用PCA算法来实现人脸特征提取&#xff0c;然后通过计算欧式距离来判别待识别测试人脸&#xff0c;本个系统框架图如下&#xff1a; 图&#xff1a; 人脸识别系统框架图 整个系统的流程是这样的&#xff0c;首先通过图像采…

给哔哩哔哩bilibili电脑版做个手机遥控器

前言 bilibili电脑版可以在电脑屏幕上观看bilibili视频。然而&#xff0c;电脑版的bilibili不能通过手机控制视频翻页和调节音量&#xff0c;这意味着观看视频时需要一直坐在电脑旁边。那么&#xff0c;有没有办法制作一个手机遥控器来控制bilibili电脑版呢&#xff1f; 首先…

基于SpringBoot+Vue+uniapp的时间管理小程序的详细设计和实现(源码+lw+部署文档+讲解等)

详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不…

【文件加密系统】华企盾DSC服务程序启动失败解决办法

问题原因&#xff1a; 1.sa账户密码错误导致连接数据数据库失败无法启动DSC服务 解决方法&#xff1a; 用windows身份验证进入数据库更改sa用户密码&#xff1a;安全性>登录名>sa>右键属性>更改密码 ※如果显示请输入秘钥更改&#xff0c;使用更改完密码的sa账户登…