Mayo commited on
Commit
1f37dd2
·
unverified ·
1 Parent(s): 749b843

feat: split encoder and decoder to reuse encoder outputs for manga-ocr, close #7

Browse files
manga-ocr/src/main.rs CHANGED
@@ -13,7 +13,10 @@ struct Args {
13
  image: String,
14
 
15
  #[arg(long)]
16
- model: String,
 
 
 
17
 
18
  #[arg(long)]
19
  vocab: String,
@@ -22,10 +25,15 @@ struct Args {
22
  fn main() -> anyhow::Result<()> {
23
  let args = Args::parse();
24
 
25
- let model = Session::builder()?
 
 
 
 
 
26
  .with_optimization_level(GraphOptimizationLevel::Level3)?
27
  .with_intra_threads(4)?
28
- .commit_from_file(args.model)?;
29
 
30
  let vocab = fs::read_to_string(args.vocab)
31
  .map_err(|e| anyhow::anyhow!("Failed to read vocab file: {e}"))?
@@ -51,6 +59,12 @@ fn main() -> anyhow::Result<()> {
51
  tensor[[0, 2, y, x]] = (pixel[2] as f32 / 255.0 - 0.5) / 0.5;
52
  }
53
 
 
 
 
 
 
 
54
  // generate
55
  let mut token_ids: Vec<i64> = vec![2i64]; // Start token
56
 
@@ -58,12 +72,12 @@ fn main() -> anyhow::Result<()> {
58
  // Create input tensors
59
  let input = Array::from_shape_vec((1, token_ids.len()), token_ids.clone())?;
60
  let inputs = inputs! {
61
- "image" => tensor.view(),
62
- "token_ids" => input,
63
  }?;
64
 
65
  // Run inference
66
- let outputs = model.run(inputs)?;
67
 
68
  // Extract logits from output
69
  let logits = outputs["logits"].try_extract_tensor::<f32>()?;
 
13
  image: String,
14
 
15
  #[arg(long)]
16
+ encoder_model: String,
17
+
18
+ #[arg(long)]
19
+ decoder_model: String,
20
 
21
  #[arg(long)]
22
  vocab: String,
 
25
  fn main() -> anyhow::Result<()> {
26
  let args = Args::parse();
27
 
28
+ let encoder_model = Session::builder()?
29
+ .with_optimization_level(GraphOptimizationLevel::Level3)?
30
+ .with_intra_threads(4)?
31
+ .commit_from_file(args.encoder_model)?;
32
+
33
+ let decoder_model = Session::builder()?
34
  .with_optimization_level(GraphOptimizationLevel::Level3)?
35
  .with_intra_threads(4)?
36
+ .commit_from_file(args.decoder_model)?;
37
 
38
  let vocab = fs::read_to_string(args.vocab)
39
  .map_err(|e| anyhow::anyhow!("Failed to read vocab file: {e}"))?
 
59
  tensor[[0, 2, y, x]] = (pixel[2] as f32 / 255.0 - 0.5) / 0.5;
60
  }
61
 
62
+ let inputs = inputs! {
63
+ "pixel_values" => tensor.view(),
64
+ }?;
65
+ let outputs = encoder_model.run(inputs)?;
66
+ let encoder_hidden_states = outputs[0].try_extract_tensor::<f32>()?;
67
+
68
  // generate
69
  let mut token_ids: Vec<i64> = vec![2i64]; // Start token
70
 
 
72
  // Create input tensors
73
  let input = Array::from_shape_vec((1, token_ids.len()), token_ids.clone())?;
74
  let inputs = inputs! {
75
+ "encoder_hidden_states" => encoder_hidden_states.view(),
76
+ "input_ids" => input,
77
  }?;
78
 
79
  // Run inference
80
+ let outputs = decoder_model.run(inputs)?;
81
 
82
  // Extract logits from output
83
  let logits = outputs["logits"].try_extract_tensor::<f32>()?;
scripts/manga_ocr_onnx_inference.py CHANGED
@@ -1,19 +1,27 @@
1
  import re
2
  import jaconv
3
  import numpy as np
 
4
 
5
  from onnxruntime import InferenceSession
6
  from PIL import Image
7
 
8
 
9
  class MangaOCR:
10
- def __init__(self, model_path: str, vocab_path: str):
11
- self.session = InferenceSession(model_path)
 
12
  self.vocab = self._load_vocab(vocab_path)
13
 
14
  def __call__(self, image: Image.Image) -> str:
15
  image = self._preprocess(image)
 
 
 
16
  token_ids = self._generate(image)
 
 
 
17
  text = self._decode(token_ids)
18
  text = self._postprocess(text)
19
 
@@ -43,14 +51,18 @@ class MangaOCR:
43
  return image
44
 
45
  def _generate(self, image: np.ndarray) -> np.ndarray:
 
 
 
 
46
  token_ids = [2]
47
 
48
  for _ in range(300):
49
- [logits] = self.session.run(
50
- output_names=["logits"],
51
- input_feed={
52
- "image": image,
53
- "token_ids": np.array([token_ids]),
54
  },
55
  )
56
 
@@ -87,11 +99,12 @@ if __name__ == "__main__":
87
 
88
  parser = argparse.ArgumentParser(description="Manga OCR with ONNX Runtime")
89
  parser.add_argument("--image", type=str, help="Path to the input image")
90
- parser.add_argument("--model", type=str, help="Path to the ONNX model file")
 
91
  parser.add_argument("--vocab", type=str, help="Path to the vocabulary file")
92
  args = parser.parse_args()
93
 
94
- ocr = MangaOCR(args.model, args.vocab)
95
  image = Image.open(args.image)
96
  text = ocr(image)
97
  print(text)
 
1
  import re
2
  import jaconv
3
  import numpy as np
4
+ import time
5
 
6
  from onnxruntime import InferenceSession
7
  from PIL import Image
8
 
9
 
10
  class MangaOCR:
11
+ def __init__(self, encoder_model_path: str, decoder_model_path: str, vocab_path: str):
12
+ self.encoder_session = InferenceSession(encoder_model_path)
13
+ self.decoder_session = InferenceSession(decoder_model_path)
14
  self.vocab = self._load_vocab(vocab_path)
15
 
16
  def __call__(self, image: Image.Image) -> str:
17
  image = self._preprocess(image)
18
+
19
+ # count time
20
+ start = time.time()
21
  token_ids = self._generate(image)
22
+ end = time.time()
23
+ print(f"Time taken: {end - start:.2f} seconds")
24
+
25
  text = self._decode(token_ids)
26
  text = self._postprocess(text)
27
 
 
51
  return image
52
 
53
  def _generate(self, image: np.ndarray) -> np.ndarray:
54
+ encoder_hidden_states = self.encoder_session.run(None, {
55
+ "pixel_values": image,
56
+ })[0]
57
+
58
  token_ids = [2]
59
 
60
  for _ in range(300):
61
+ [logits] = self.decoder_session.run(
62
+ None,
63
+ {
64
+ "encoder_hidden_states": encoder_hidden_states,
65
+ "input_ids": np.array([token_ids], dtype=np.int64),
66
  },
67
  )
68
 
 
99
 
100
  parser = argparse.ArgumentParser(description="Manga OCR with ONNX Runtime")
101
  parser.add_argument("--image", type=str, help="Path to the input image")
102
+ parser.add_argument("--encoder-model", type=str, help="Path to the ONNX model file")
103
+ parser.add_argument("--decoder-model", type=str, help="Path to the ONNX model file")
104
  parser.add_argument("--vocab", type=str, help="Path to the vocabulary file")
105
  args = parser.parse_args()
106
 
107
+ ocr = MangaOCR(args.encoder_model, args.decoder_model, args.vocab)
108
  image = Image.open(args.image)
109
  text = ocr(image)
110
  print(text)
src-tauri/src/manga_ocr.rs CHANGED
@@ -6,21 +6,28 @@ use ort::{inputs, session::Session};
6
 
7
  #[derive(Debug)]
8
  pub struct MangaOCR {
9
- model: Session,
 
10
  vocab: Vec<String>,
11
  }
12
 
13
  impl MangaOCR {
14
  pub fn new() -> anyhow::Result<Self> {
15
  let api = Api::new()?;
16
- let repo = api.model("mayocream/koharu".to_string());
17
- let model_path = repo.get("manga-ocr.onnx")?;
 
18
  let vocab_path = repo.get("vocab.txt")?;
19
 
20
- let model = Session::builder()?
21
  .with_optimization_level(ort::session::builder::GraphOptimizationLevel::Level3)?
22
  .with_intra_threads(thread::available_parallelism()?.get())?
23
- .commit_from_file(model_path)?;
 
 
 
 
 
24
 
25
  let vocab = std::fs::read_to_string(vocab_path)
26
  .map_err(|e| anyhow::anyhow!("Failed to read vocab file: {e}"))?
@@ -28,7 +35,11 @@ impl MangaOCR {
28
  .map(|s| s.to_string())
29
  .collect::<Vec<_>>();
30
 
31
- Ok(Self { model, vocab })
 
 
 
 
32
  }
33
 
34
  pub fn inference(&self, image: &image::DynamicImage) -> anyhow::Result<String> {
@@ -48,6 +59,13 @@ impl MangaOCR {
48
  tensor[[0, 2, y, x]] = (pixel[2] as f32 / 255.0 - 0.5) / 0.5;
49
  }
50
 
 
 
 
 
 
 
 
51
  // generate
52
  let mut token_ids: Vec<i64> = vec![2i64]; // Start token
53
 
@@ -55,12 +73,12 @@ impl MangaOCR {
55
  // Create input tensors
56
  let input = ndarray::Array::from_shape_vec((1, token_ids.len()), token_ids.clone())?;
57
  let inputs = inputs! {
58
- "image" => tensor.view(),
59
- "token_ids" => input,
60
  }?;
61
 
62
  // Run inference
63
- let outputs = self.model.run(inputs)?;
64
 
65
  // Extract logits from output
66
  let logits = outputs["logits"].try_extract_tensor::<f32>()?;
 
6
 
7
  #[derive(Debug)]
8
  pub struct MangaOCR {
9
+ encoder_model: Session,
10
+ decoder_model: Session,
11
  vocab: Vec<String>,
12
  }
13
 
14
  impl MangaOCR {
15
  pub fn new() -> anyhow::Result<Self> {
16
  let api = Api::new()?;
17
+ let repo = api.model("mayocream/manga-ocr-onnx".to_string());
18
+ let encoder_model_path = repo.get("encoder_model.onnx")?;
19
+ let decoder_model_path = repo.get("decoder_model.onnx")?;
20
  let vocab_path = repo.get("vocab.txt")?;
21
 
22
+ let encoder_model = Session::builder()?
23
  .with_optimization_level(ort::session::builder::GraphOptimizationLevel::Level3)?
24
  .with_intra_threads(thread::available_parallelism()?.get())?
25
+ .commit_from_file(encoder_model_path)?;
26
+
27
+ let decoder_model = Session::builder()?
28
+ .with_optimization_level(ort::session::builder::GraphOptimizationLevel::Level3)?
29
+ .with_intra_threads(thread::available_parallelism()?.get())?
30
+ .commit_from_file(decoder_model_path)?;
31
 
32
  let vocab = std::fs::read_to_string(vocab_path)
33
  .map_err(|e| anyhow::anyhow!("Failed to read vocab file: {e}"))?
 
35
  .map(|s| s.to_string())
36
  .collect::<Vec<_>>();
37
 
38
+ Ok(Self {
39
+ encoder_model,
40
+ decoder_model,
41
+ vocab,
42
+ })
43
  }
44
 
45
  pub fn inference(&self, image: &image::DynamicImage) -> anyhow::Result<String> {
 
59
  tensor[[0, 2, y, x]] = (pixel[2] as f32 / 255.0 - 0.5) / 0.5;
60
  }
61
 
62
+ // save encoder hidden state
63
+ let inputs = inputs! {
64
+ "pixel_values" => tensor.view(),
65
+ }?;
66
+ let outputs = self.encoder_model.run(inputs)?;
67
+ let encoder_hidden_state = outputs[0].try_extract_tensor::<f32>()?;
68
+
69
  // generate
70
  let mut token_ids: Vec<i64> = vec![2i64]; // Start token
71
 
 
73
  // Create input tensors
74
  let input = ndarray::Array::from_shape_vec((1, token_ids.len()), token_ids.clone())?;
75
  let inputs = inputs! {
76
+ "encoder_hidden_states" => encoder_hidden_state.view(),
77
+ "input_ids" => input,
78
  }?;
79
 
80
  // Run inference
81
+ let outputs = self.decoder_model.run(inputs)?;
82
 
83
  // Extract logits from output
84
  let logits = outputs["logits"].try_extract_tensor::<f32>()?;