歡迎訪問我的GitHub
https://github.com/zq2599/blog_demos
內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等;
本篇概覽
- 在《三分鐘:極速體驗JAVA版目標檢測(YOLO4)》一文中,我們們體驗了YOLO4強大的物體識別能力,如下圖,原圖中的狗子、人、馬都被識別並標註出來了:
- 如果您之前對深度學習和YOLO、darknet等有過了解,相信您會產生疑問:Java能實現這些?
- 沒錯,今天我們們就從零開始,開發一個SpringBoot應用實現上述功能,該應用名為<font color="blue">yolo-demo</font>
- 讓SpringBoot應用識別圖片中的物體,其關鍵在如何使用已經訓練好的神經網路模型,好在OpenCV整合的DNN模組可以載入和使用YOLO4模型,我們只要找到使用OpenCV的辦法即可
- 我這裡的方法是使用JavaCV庫,因為JavaCV本身封裝了OpenCV,最終可以使用YOLO4模型進行推理,依賴情況如下圖所示:
關鍵技術
- 本篇涉及到JavaCV、OpenCV、YOLO4等,從上圖可以看出JavaCV已將這些做了封裝,包括最終推理時所用的模型也是YOLO4官方提前訓練好的,我們們只要知道如何使用JavaCV的API即可
- YOVO4的paper在此:https://arxiv.org/pdf/2004.10...
版本資訊
- 這裡給出我的開發環境供您參考:
- 作業系統:Ubuntu 16(MacBook Pro也可以,版本是11.2.3,macOS Big Sur)
- docker:20.10.2 Community
- java:1.8.0_211
- springboot:2.4.8
- javacv:1.5.6
- opencv:4.5.3
實戰步驟
- 在正式動手前,先把本次實戰的步驟梳理清楚,後面按部就班執行即可;
- 為了減少環境和軟體差異的影響,讓程式的執行除錯更簡單,這裡會把SpringBoot應用製作成docker映象,然後在docker環境執行,所以,整個實戰簡單來說分為三步 :製做基礎映象、開發SpringBoot應用、把應用做成映象,如下圖:
- 上述流程中的第一步<font color="blue">製做基礎映象</font>,已經在《製作JavaCV應用依賴的基礎Docker映象(CentOS7+JDK8+OpenCV4)》一文中詳細介紹,我們們直接使用映象<font color="red">bolingcavalry/opencv4.5.3:0.0.1</font>即可,接下來的內容將會聚焦SpringBoot應用的開發;
- 這個SpringBoot應用的功能很單一,如下圖所示:
- 整個開發過程涉及到這些步驟:提交照片的網頁、神經網路初始化、檔案處理、圖片檢測、處理檢測結果、在圖片上標準識別結果、前端展示圖片等,完整步驟已經整理如下圖:
- 內容很豐富,收穫也不會少,更何況前文已確保可以成功執行,那麼,別猶豫啦,我們們開始吧!
原始碼下載
- 本篇實戰中的完整原始碼可在GitHub下載到,地址和連結資訊如下表所示(https://github.com/zq2599/blo...):
名稱 | 連結 | 備註 |
---|---|---|
專案主頁 | https://github.com/zq2599/blo... | 該專案在GitHub上的主頁 |
git倉庫地址(https) | https://github.com/zq2599/blo... | 該專案原始碼的倉庫地址,https協議 |
git倉庫地址(ssh) | git@github.com:zq2599/blog_demos.git | 該專案原始碼的倉庫地址,ssh協議 |
- 這個git專案中有多個資料夾,本篇的原始碼在<font color="blue">javacv-tutorials</font>資料夾下,如下圖紅框所示:
- <font color="blue">javacv-tutorials</font>裡面有多個子工程,今天的程式碼在<font color="red">yolo-demo</font>工程下:
新建SpringBoot應用
- 新建名為<font color="blue">yolo-demo</font>的maven工程,首先這是個標準的SpringBoot工程,其次新增了javacv的依賴庫,pom.xml內容如下,重點是javacv、opencv等庫的依賴和準確的版本匹配:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>yolo-demo</artifactId>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>
<springboot.version>2.4.8</springboot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--FreeMarker模板檢視依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv-platform-gpu</artifactId>
<version>4.5.3-1.5.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 如果父工程不是springboot,就要用以下方式使用外掛,才能生成正常的jar -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.bolingcavalry.yolodemo.YoloDemoApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- 接下來的重點是配置檔案<font color="blue">application.properties</font>,如下可見,除了常見的spring配置,還有幾個檔案路徑配置,實際執行時,這些路徑都要存放對應的檔案給程式使用,這些檔案如何獲取稍後會講到:
### FreeMarker 配置
spring.freemarker.allow-request-override=false
#Enable template caching.啟用模板快取。
spring.freemarker.cache=false
spring.freemarker.check-template-location=true
spring.freemarker.charset=UTF-8
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.expose-spring-macro-helpers=false
#設定皮膚字尾
spring.freemarker.suffix=.ftl
# 設定單個檔案最大記憶體
spring.servlet.multipart.max-file-size=100MB
# 設定所有檔案最大記憶體
spring.servlet.multipart.max-request-size=1000MB
# 自定義檔案上傳路徑
web.upload-path=/app/images
# 模型路徑
# yolo的配置檔案所在位置
opencv.yolo-cfg-path=/app/model/yolov4.cfg
# yolo的模型檔案所在位置
opencv.yolo-weights-path=/app/model/yolov4.weights
# yolo的分類檔案所在位置
opencv.yolo-coconames-path=/app/model/coco.names
# yolo模型推理時的圖片寬度
opencv.yolo-width=608
# yolo模型推理時的圖片高度
opencv.yolo-height=608
- 啟動類<font color="blue">YoloDemoApplication.java</font>:
package com.bolingcavalry.yolodemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class YoloDemoApplication {
public static void main(String[] args) {
SpringApplication.run(YoloDemoApplication.class, args);
}
}
- 工程已建好,接下來開始編碼,先從前端頁面開始
前端頁面
- 只要涉及到前端,欣宸一般都會發個自保宣告:請大家原諒欣宸不入流的前端水平,頁面做得我自己都不忍直視,但為了功能的完整,請您忍忍,也不是不能用,我們們總要有個地方提交照片並且展示識別結果不是?
- 新增名為<font color="blue">index.ftl</font>的前端模板檔案,位置如下圖紅框:
- <font color="blue">index.ftl</font>的內容如下,可見很簡單,有選擇和提交檔案的表單,也有展示結果的指令碼,還能展示後臺返回的提示資訊,嗯嗯,這就夠用了:
<!DOCTYPE html>
<head>
<meta charset="UTF-8" />
<title>圖片上傳Demo</title>
</head>
<body>
<h1 >圖片上傳Demo</h1>
<form action="fileUpload" method="post" enctype="multipart/form-data">
<p>選擇檢測檔案: <input type="file" name="fileName"/></p>
<p><input type="submit" value="提交"/></p>
</form>
<#--判斷是否上傳檔案-->
<#if msg??>
<span>${msg}</span><br><br>
<#else >
<span>${msg!("檔案未上傳")}</span><br>
</#if>
<#--顯示圖片,一定要在img中的src發請求給controller,否則直接跳轉是亂碼-->
<#if fileName??>
<#--<img src="/show?fileName=${fileName}" style="width: 100px"/>-->
<img src="/show?fileName=${fileName}"/>
<#else>
<#--<img src="/show" style="width: 200px"/>-->
</#if>
</body>
</html>
- 頁面的效果,就像下面這樣:
後端邏輯:初始化
- 為了保持簡單,所有後端邏輯放在一個java檔案中:YoloServiceController.java,按照前面梳理的流程,我們們先看初始化部分
- 首先是成員變數和依賴
private final ResourceLoader resourceLoader;
@Autowired
public YoloServiceController(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Value("${web.upload-path}")
private String uploadPath;
@Value("${opencv.yolo-cfg-path}")
private String cfgPath;
@Value("${opencv.yolo-weights-path}")
private String weightsPath;
@Value("${opencv.yolo-coconames-path}")
private String namesPath;
@Value("${opencv.yolo-width}")
private int width;
@Value("${opencv.yolo-height}")
private int height;
/**
* 置信度門限(超過這個值才認為是可信的推理結果)
*/
private float confidenceThreshold = 0.5f;
private float nmsThreshold = 0.4f;
// 神經網路
private Net net;
// 輸出層
private StringVector outNames;
// 分類名稱
private List<String> names;
- 接下來是初始化方法init,可見會從之前配置的幾個檔案路徑中載入神經網路所需的配置、訓練模型等檔案,關鍵方法是readNetFromDarknet的呼叫,還有就是檢查是否有支援CUDA的裝置,如果有就在神經網路中做好設定:
@PostConstruct
private void init() throws Exception {
// 初始化列印一下,確保編碼正常,否則日誌輸出會是亂碼
log.error("file.encoding is " + System.getProperty("file.encoding"));
// 神經網路初始化
net = readNetFromDarknet(cfgPath, weightsPath);
// 檢查網路是否為空
if (net.empty()) {
log.error("神經網路初始化失敗");
throw new Exception("神經網路初始化失敗");
}
// 輸出層
outNames = net.getUnconnectedOutLayersNames();
// 檢查GPU
if (getCudaEnabledDeviceCount() > 0) {
net.setPreferableBackend(opencv_dnn.DNN_BACKEND_CUDA);
net.setPreferableTarget(opencv_dnn.DNN_TARGET_CUDA);
}
// 分類名稱
try {
names = Files.readAllLines(Paths.get(namesPath));
} catch (IOException e) {
log.error("獲取分類名稱失敗,檔案路徑[{}]", namesPath, e);
}
}
處理上傳檔案
- 前端將二進位制格式的圖片檔案提交上來後如何處理?這裡整理了一個簡單的檔案處理方法upload,會將檔案儲存在伺服器的指定位置,後面會呼叫:
/**
* 上傳檔案到指定目錄
* @param file 檔案
* @param path 檔案存放路徑
* @param fileName 原始檔名
* @return
*/
private static boolean upload(MultipartFile file, String path, String fileName){
//使用原檔名
String realPath = path + "/" + fileName;
File dest = new File(realPath);
//判斷檔案父目錄是否存在
if(!dest.getParentFile().exists()){
dest.getParentFile().mkdir();
}
try {
//儲存檔案
file.transferTo(dest);
return true;
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
物體檢測
- 準備工作都完成了,來寫最核心的物體檢測程式碼,這些程式碼放在yolo-demo應用處理web請求的方法中,如下所示,可見這裡只是個大綱,將推理、結果處理、圖片標註等功能串起來形成完整流程,但是不涉及每個具體功能的細節:
@RequestMapping("fileUpload")
public String upload(@RequestParam("fileName") MultipartFile file, Map<String, Object> map){
log.info("檔案 [{}], 大小 [{}]", file.getOriginalFilename(), file.getSize());
// 檔名稱
String originalFileName = file.getOriginalFilename();
if (!upload(file, uploadPath, originalFileName)){
map.put("msg", "上傳失敗!");
return "forward:/index";
}
// 讀取檔案到Mat
Mat src = imread(uploadPath + "/" + originalFileName);
// 執行推理
MatVector outs = doPredict(src);
// 處理原始的推理結果,
// 對檢測到的每個目標,找出置信度最高的類別作為改目標的類別,
// 還要找出每個目標的位置,這些資訊都儲存在ObjectDetectionResult物件中
List<ObjectDetectionResult> results = postprocess(src, outs);
// 釋放資源
outs.releaseReference();
// 檢測到的目標總數
int detectNum = results.size();
log.info("一共檢測到{}個目標", detectNum);
// 沒檢測到
if (detectNum<1) {
// 顯示圖片
map.put("msg", "未檢測到目標");
// 檔名
map.put("fileName", originalFileName);
return "forward:/index";
} else {
// 檢測結果頁面的提示資訊
map.put("msg", "檢測到" + results.size() + "個目標");
}
// 計算出總耗時,並輸出在圖片的左上角
printTimeUsed(src);
// 將每一個被識別的物件在圖片框出來,並在框的左上角標註該物件的類別
markEveryDetectObject(src, results);
// 將新增了標註的圖片保持在磁碟上,並將圖片資訊寫入map(給跳轉頁面使用)
saveMarkedImage(map, src);
return "forward:/index";
}
- 這裡已經可以把整個流程弄明白了,接下來展開每個細節
用神經網路檢測物體
- 由上面的程式碼可見,圖片被轉為Mat物件後(OpenCV中的重要資料結構,可以理解為矩陣,裡面存放著圖片每個畫素的資訊),被送入<font color="blue">doPredict</font>方法,該方法執行完畢後就得到了物體識別的結果
- 細看doPredict方法,可見核心是用blobFromImage方法得到四維blob物件,再將這個物件送給神經網路去檢測(net.setInput、net.forward)
/**
* 用神經網路執行推理
* @param src
* @return
*/
private MatVector doPredict(Mat src) {
// 將圖片轉為四維blog,並且對尺寸做調整
Mat inputBlob = blobFromImage(src,
1 / 255.0,
new Size(width, height),
new Scalar(0.0),
true,
false,
CV_32F);
// 神經網路輸入
net.setInput(inputBlob);
// 設定輸出結果儲存的容器
MatVector outs = new MatVector(outNames.size());
// 推理,結果儲存在outs中
net.forward(outs, outNames);
// 釋放資源
inputBlob.release();
return outs;
}
- 要注意的是,blobFromImage、net.setInput、net.forward這些都是native方法,是OpenCV的dnn模組提供的
- doPredict方法返回的是MatVector物件,這裡面就是檢測結果
處理原始檢測結果
- 檢測結果MatVector物件是個集合,裡面有多個Mat物件,每個Mat物件是一個表格,裡面有豐富的資料,具體的內容如下圖:
- 看過上圖後,相信您對如何處理原始的檢測結果已經胸有成竹了,只要從MatVector中逐個取出Mat,把每個Mat當做表格,將表格每一行中概率最大的列找到,此列就是該物體的類別了(至於每一列到底是啥東西,為啥上面表格中第五列是人,第六列是自行車,最後一列是牙刷?這個稍後會講到):
/**
* 推理完成後的操作
* @param frame
* @param outs
* @return
*/
private List<ObjectDetectionResult> postprocess(Mat frame, MatVector outs) {
final IntVector classIds = new IntVector();
final FloatVector confidences = new FloatVector();
final RectVector boxes = new RectVector();
// 處理神經網路的輸出結果
for (int i = 0; i < outs.size(); ++i) {
// extract the bounding boxes that have a high enough score
// and assign their highest confidence class prediction.
// 每個檢測到的物體,都有對應的每種型別的置信度,取最高的那種
// 例如檢車到貓的置信度百分之九十,狗的置信度百分之八十,那就認為是貓
Mat result = outs.get(i);
FloatIndexer data = result.createIndexer();
// 將檢測結果看做一個表格,
// 每一行表示一個物體,
// 前面四列表示這個物體的座標,後面的每一列,表示這個物體在某個類別上的置信度,
// 每行都是從第五列開始遍歷,找到最大值以及對應的列號,
for (int j = 0; j < result.rows(); j++) {
// minMaxLoc implemented in java because it is 1D
int maxIndex = -1;
float maxScore = Float.MIN_VALUE;
for (int k = 5; k < result.cols(); k++) {
float score = data.get(j, k);
if (score > maxScore) {
maxScore = score;
maxIndex = k - 5;
}
}
// 如果最大值大於之前設定的置信度門限,就表示可以確定是這類物體了,
// 然後就把這個物體相關的識別資訊儲存下來,要儲存的資訊有:類別、置信度、座標
if (maxScore > confidenceThreshold) {
int centerX = (int) (data.get(j, 0) * frame.cols());
int centerY = (int) (data.get(j, 1) * frame.rows());
int width = (int) (data.get(j, 2) * frame.cols());
int height = (int) (data.get(j, 3) * frame.rows());
int left = centerX - width / 2;
int top = centerY - height / 2;
// 儲存類別
classIds.push_back(maxIndex);
// 儲存置信度
confidences.push_back(maxScore);
// 儲存座標
boxes.push_back(new Rect(left, top, width, height));
}
}
// 資源釋放
data.release();
result.release();
}
// remove overlapping bounding boxes with NMS
IntPointer indices = new IntPointer(confidences.size());
FloatPointer confidencesPointer = new FloatPointer(confidences.size());
confidencesPointer.put(confidences.get());
// 非極大值抑制
NMSBoxes(boxes, confidencesPointer, confidenceThreshold, nmsThreshold, indices, 1.f, 0);
// 將檢測結果放入BO物件中,便於業務處理
List<ObjectDetectionResult> detections = new ArrayList<>();
for (int i = 0; i < indices.limit(); ++i) {
final int idx = indices.get(i);
final Rect box = boxes.get(idx);
final int clsId = classIds.get(idx);
detections.add(new ObjectDetectionResult(
clsId,
names.get(clsId),
confidences.get(idx),
box.x(),
box.y(),
box.width(),
box.height()
));
// 釋放資源
box.releaseReference();
}
// 釋放資源
indices.releaseReference();
confidencesPointer.releaseReference();
classIds.releaseReference();
confidences.releaseReference();
boxes.releaseReference();
return detections;
}
- 可見程式碼很簡單,就是把每個Mat當做表格來處理,有兩處特別的地方要處理:
- confidenceThreshold變數,置信度門限,這裡是0.5,如果某一行的最大概率連0.5都達不到,那就相當於已知所有類別的可能性都不大,那就不算識別出來了,所以不會存入detections集合中(不會在結果圖片中標註)
- NMSBoxes:分類器進化為檢測器時,在原始影像上從多個尺度產生視窗,這就導致下圖左側的效果,同一個人檢測了多張人臉,此時用NMSBoxes來保留最優的一個結果
- 現在解釋一下Mat物件對應的表格中,每一列到底是什麼類別:這個表格是YOLO4的檢測結果,所以每一列是什麼類別應該由YOLO4來解釋,官方提供了名為<font color="blue">coco.names</font>的檔案,該檔案的內容如下圖,一共80行,每一行是表示一個類別:
- 此刻聰明的您肯定已經明白Mat表格中的每一列代表什麼類別了:Mat表格中的每一列對應<font color="blue">coco.names</font>的每一行,如下圖:
- postprocess方法執行完畢後,一張照片的識別結果就被放入名為detections的集合中,該集合內的每個元素代表一個識別出的物體,來看看這個元素的資料結構,如下所示,這些資料夠我們在照片上標註識別結果了:
@Data
@AllArgsConstructor
public class ObjectDetectionResult {
// 類別索引
int classId;
// 類別名稱
String className;
// 置信度
float confidence;
// 物體在照片中的橫座標
int x;
// 物體在照片中的縱座標
int y;
// 物體寬度
int width;
// 物體高度
int height;
}
把檢測結果畫在圖片上
- 手裡有了檢測結果,接下來要做的就是將這些結果畫在原圖上,這樣就有了物體識別的效果,畫圖分兩部分,首先是左上角的總耗時,其次是每個物體識別結果
- 先在圖片的上角畫出本次檢測的總耗時,效果如下圖所示:
- 負責畫出總耗時的是printTimeUsed方法,如下,可見總耗時是用多層網路的總次數除以頻率得到的,注意,這不是網頁上的介面總耗時,而是神經網路識別物體的總耗時,例外畫圖的putText是個本地方法,這也是OpenCV的常用方法之一:
/**
* 計算出總耗時,並輸出在圖片的左上角
* @param src
*/
private void printTimeUsed(Mat src) {
// 總次數
long totalNums = net.getPerfProfile(new DoublePointer());
// 頻率
double freq = getTickFrequency()/1000;
// 總次數除以頻率就是總耗時
double t = totalNums / freq;
// 將本次檢測的總耗時列印在展示影像的左上角
putText(src,
String.format("Inference time : %.2f ms", t),
new Point(10, 20),
FONT_HERSHEY_SIMPLEX,
0.6,
new Scalar(255, 0, 0, 0),
1,
LINE_AA,
false);
}
- 接下來是畫出每個物體識別的結果,有了ObjectDetectionResult物件集合,畫圖就非常簡單了:呼叫畫矩形和文字的本地方法即可:
/**
* 將每一個被識別的物件在圖片框出來,並在框的左上角標註該物件的類別
* @param src
* @param results
*/
private void markEveryDetectObject(Mat src, List<ObjectDetectionResult> results) {
// 在圖片上標出每個目標以及類別和置信度
for(ObjectDetectionResult result : results) {
log.info("類別[{}],置信度[{}%]", result.getClassName(), result.getConfidence() * 100f);
// annotate on image
rectangle(src,
new Point(result.getX(), result.getY()),
new Point(result.getX() + result.getWidth(), result.getY() + result.getHeight()),
Scalar.MAGENTA,
1,
LINE_8,
0);
// 寫在目標左上角的內容:類別+置信度
String label = result.getClassName() + ":" + String.format("%.2f%%", result.getConfidence() * 100f);
// 計算顯示這些內容所需的高度
IntPointer baseLine = new IntPointer();
Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, baseLine);
int top = Math.max(result.getY(), labelSize.height());
// 新增內容到圖片上
putText(src, label, new Point(result.getX(), top-4), FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 255, 0, 0), 1, LINE_4, false);
}
}
展示結果
- 核心工作已經完成,接下來就是儲存圖片再跳轉到展示網頁:
- 至此SpringBoot工程編碼完成,接下來要做的就是將整個工程做成docker映象
將SpringBoot工程做成docker映象
- 前面《製作JavaCV應用依賴的基礎Docker映象(CentOS7+JDK8+OpenCV4)》做好了基礎映象,幫我們準備好了JDK和OpenCV庫,使得接下來的操作格外簡單,我們們一步一步來
- 先編寫Dockerfile檔案,Dockerfile檔案請放在<font color="blue">和pom.xml同一目錄</font>,內容如下:
# 基礎映象整合了openjdk8和opencv4.5.3
FROM bolingcavalry/opencv4.5.3:0.0.1
# 建立目錄
RUN mkdir -p /app/images && mkdir -p /app/model
# 指定映象的內容的來源位置
ARG DEPENDENCY=target/dependency
# 複製內容到映象
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENV LANG C.UTF-8
ENV LANGUAGE zh_CN.UTF-8
ENV LC_ALL C.UTF-8
ENV TZ Asia/Shanghai
# 指定啟動命令(注意要執行編碼,否則日誌是亂碼)
ENTRYPOINT ["java","-Dfile.encoding=utf-8","-cp","app:app/lib/*","com.bolingcavalry.yolodemo.YoloDemoApplication"]
- 控制檯進入pom.xml所在目錄,執行命令<font color="blue">mvn clean package -U</font>,這是個普通的maven命令,會編譯原始碼,在target目錄下生成檔案<font color="red">yolo-demo-1.0-SNAPSHOT.jar</font>
- 執行以下命令,可以從jar檔案中提取出製作docker映象所需的內容:
mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
- 執行以下命令即可構建映象:
docker build -t bolingcavalry/yolodemo:0.0.1 .
- 構建成功:
will@willMini yolo-demo % docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
bolingcavalry/yolodemo 0.0.1 d0ef6e734b53 About a minute ago 2.99GB
bolingcavalry/opencv4.5.3 0.0.1 d1518ffa4699 6 days ago 2.01GB
- 此刻,具備完整物體識別能力的SpringBoot應用已經開發完成了,還記得application.properties中的那幾個檔案路徑配置麼?我們們要去下載這幾個檔案,有兩種下載方式,您二選一即可
- 第一種是從官方下載,從下面這三個地址分別下下載:
- YOLOv4配置檔案: https://raw.githubusercontent...
- YOLOv4權重: https://github.com/AlexeyAB/d...
- 分類名稱: https://raw.githubusercontent...
- 第二種是從csdn下載(無需積分),上述三個檔案我已打包放在此:https://download.csdn.net/dow...
- 上述兩種方式無論哪種,最終都會得到三個檔案:yolov4.cfg、yolov4.weights、coco.names,請將它們放在同一目錄下,我是放在這裡:/home/will/temp/202110/19/model
- 新建一個目錄用來存放照片,我這裡新建的目錄是:<font color="blue">/home/will/temp/202110/19/images</font>,注意要確保該目錄可以讀寫
最終目錄結構如下所示:
/home/will/temp/202110/19/
├── images
└── model
├── coco.names
├── yolov4.cfg
└── yolov4.weights
- 萬事俱備,執行以下命令即可執行服務:
sudo docker run \
--rm \
--name yolodemo \
-p 8080:8080 \
-v /home/will/temp/202110/19/images:/app/images \
-v /home/will/temp/202110/19/model:/app/model \
bolingcavalry/yolodemo:0.0.1
- 服務執行起來後,操作過程和效果與《三分鐘:極速體驗JAVA版目標檢測(YOLO4)》一文完全相同,就不多贅述了
- 至此,整個物體識別的開發實戰就完成了,Java在工程化方面的便利性,再結合深度學習領域的優秀模型,為我們們解決視覺影像問題增加了一個備選方案,如果您是一位對視覺和影像感興趣的Java程式設計師,希望本文能給您一些參考
你不孤單,欣宸原創一路相伴
歡迎關注公眾號:程式設計師欣宸
微信搜尋「程式設計師欣宸」,我是欣宸,期待與您一同暢遊Java世界...
https://github.com/zq2599/blog_demos