Skip to content

Commit 984b485

Browse files
authored
Merge pull request #120 from alanxinn/master
Add PaddleSeg inference project library
2 parents 475ed95 + db27c80 commit 984b485

File tree

7 files changed

+291
-0
lines changed

7 files changed

+291
-0
lines changed

PaddleSharp.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdcb.PaddleNLP.Lac.Tests",
9494
EndProject
9595
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdcb.PaddleNLP.Lac.Model", "src\Sdcb.PaddleNLP.Lac.Model\Sdcb.PaddleNLP.Lac.Model.csproj", "{640420BD-CE2D-4108-B82D-8B4544FC2FB0}"
9696
EndProject
97+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sdcb.PaddleSeg", "src\Sdcb.PaddleSeg\Sdcb.PaddleSeg.csproj", "{5B99D3D9-BDDD-4C0A-BD51-7E24B1B69DF3}"
98+
EndProject
9799
Global
98100
GlobalSection(SolutionConfigurationPlatforms) = preSolution
99101
Debug|Any CPU = Debug|Any CPU
@@ -168,6 +170,10 @@ Global
168170
{640420BD-CE2D-4108-B82D-8B4544FC2FB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
169171
{640420BD-CE2D-4108-B82D-8B4544FC2FB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
170172
{640420BD-CE2D-4108-B82D-8B4544FC2FB0}.Release|Any CPU.Build.0 = Release|Any CPU
173+
{5B99D3D9-BDDD-4C0A-BD51-7E24B1B69DF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
174+
{5B99D3D9-BDDD-4C0A-BD51-7E24B1B69DF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
175+
{5B99D3D9-BDDD-4C0A-BD51-7E24B1B69DF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
176+
{5B99D3D9-BDDD-4C0A-BD51-7E24B1B69DF3}.Release|Any CPU.Build.0 = Release|Any CPU
171177
EndGlobalSection
172178
GlobalSection(SolutionProperties) = preSolution
173179
HideSolutionNode = FALSE
@@ -195,6 +201,7 @@ Global
195201
{5756186D-613D-4656-B21D-822AB4DD9F8F} = {B3A59318-2F90-40D4-B995-7D56EB8C50F0}
196202
{19222033-C5E1-4326-A597-75CF2DE4007F} = {CA2A775C-763B-4B69-AC5B-4F90DD668E4A}
197203
{640420BD-CE2D-4108-B82D-8B4544FC2FB0} = {B3A59318-2F90-40D4-B995-7D56EB8C50F0}
204+
{5B99D3D9-BDDD-4C0A-BD51-7E24B1B69DF3} = {B3A59318-2F90-40D4-B995-7D56EB8C50F0}
198205
EndGlobalSection
199206
GlobalSection(ExtensibilityGlobals) = postSolution
200207
SolutionGuid = {083C9A35-8781-4D12-8146-B08E4A61DA8E}

src/Sdcb.PaddleSeg/BaseModel.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Sdcb.PaddleInference;
2+
3+
namespace Sdcb.PaddleSeg
4+
{
5+
public abstract class BaseModel
6+
{
7+
/// <summary>
8+
/// Create the paddle config for the model.
9+
/// </summary>
10+
/// <returns>The paddle config for model.</returns>
11+
public abstract PaddleConfig CreateConfig();
12+
13+
/// <summary>
14+
/// Gets the default device for the model.
15+
/// </summary>
16+
public abstract Action<PaddleConfig> DefaultDevice { get; }
17+
18+
/// <summary>
19+
/// Configure the device related config of the <see cref="PaddleConfig"/>.
20+
/// </summary>
21+
/// <param name="configure">The device and configure of the <see cref="PaddleConfig"/>, pass null to using model's <see cref="DefaultDevice"/>.</param>
22+
/// <param name="config">The PaddleConfig to modify.</param>
23+
public virtual void ConfigureDevice(PaddleConfig config, Action<PaddleConfig>? configure = null)
24+
{
25+
config.Apply(configure ?? DefaultDevice);
26+
}
27+
}
28+
}

src/Sdcb.PaddleSeg/FileSegModel.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Sdcb.PaddleInference;
2+
3+
namespace Sdcb.PaddleSeg
4+
{
5+
public class FileSegModel : SegModel
6+
{
7+
/// <summary>
8+
/// Gets the directory path containing the model files.
9+
/// </summary>
10+
public string DirectoryPath { get; init; }
11+
12+
/// <summary>
13+
/// Initializes a new instance of the <see cref="FileDetectionModel"/> class with the specified directory path.
14+
/// </summary>
15+
/// <param name="directoryPath">The directory path containing the model files.</param>
16+
/// <param name="version">The version of detection model.</param>
17+
public FileSegModel(string directoryPath)
18+
{
19+
DirectoryPath = directoryPath;
20+
}
21+
22+
/// <summary>
23+
/// Creates a PaddleConfig object using the model directory path.
24+
/// </summary>
25+
/// <returns>A <see cref="PaddleConfig"/> object created using <see cref="DirectoryPath"/>.</returns>
26+
public override PaddleConfig CreateConfig() => PaddleConfig.FromModelDir(DirectoryPath);
27+
}
28+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using System.Runtime.InteropServices;
2+
using OpenCvSharp;
3+
using Sdcb.PaddleInference;
4+
5+
namespace Sdcb.PaddleSeg
6+
{
7+
/// <summary>
8+
/// Handles image segmentation prediction using PaddlePaddle inference
9+
/// </summary>
10+
internal class PaddleSegPredictor : IDisposable
11+
{
12+
private readonly PaddlePredictor _p;
13+
14+
/// <summary>
15+
/// Initializes a new instance of PaddleSegPredictor
16+
/// </summary>
17+
/// <param name="model">The segmentation model to use</param>
18+
/// <param name="configure">Optional configuration action for PaddleConfig</param>
19+
public PaddleSegPredictor(SegModel model, Action<PaddleConfig>? configure = null)
20+
{
21+
PaddleConfig c = model.CreateConfig();
22+
model.ConfigureDevice(c, configure);
23+
24+
_p = c.CreatePredictor();
25+
}
26+
27+
/// <summary>
28+
/// Performs segmentation on the input image
29+
/// </summary>
30+
/// <param name="img">Input image in BGR format</param>
31+
/// <returns>Array of segmentation labels where each element represents the class ID for a pixel</returns>
32+
public int[] Run(Mat img)
33+
{
34+
// Convert BGR to RGB color space
35+
Cv2.CvtColor(img, img, ColorConversionCodes.BGR2RGB);
36+
37+
// Normalize the image
38+
Mat mat = Normalize(img);
39+
40+
using PaddlePredictor predictor = _p.Clone();
41+
using (PaddleTensor input = predictor.GetInputTensor(predictor.InputNames[0]))
42+
{
43+
// Set input shape: [batch_size=1, channels=3, height, width]
44+
input.Shape = new[] { 1, 3, mat.Rows, mat.Cols };
45+
float[] data = ExtractMat(mat);
46+
input.SetData(data);
47+
}
48+
49+
if (!predictor.Run())
50+
{
51+
throw new Exception("PaddlePredictor(Detector) run failed.");
52+
}
53+
54+
using (PaddleTensor output = predictor.GetOutputTensor(predictor.OutputNames[0]))
55+
{
56+
int[] data = output.GetData<int>();
57+
int[] shape = output.Shape;
58+
return data;
59+
}
60+
}
61+
62+
/// <summary>
63+
/// Normalizes the input image by:
64+
/// 1. Converting to float32 and scaling to [0,1]
65+
/// 2. Applying formula: (x - 0.5) / 0.5
66+
/// </summary>
67+
/// <param name="src">Source image</param>
68+
/// <returns>Normalized image</returns>
69+
private static Mat Normalize(Mat src)
70+
{
71+
Mat imFloat = new Mat();
72+
src.ConvertTo(imFloat, MatType.CV_32FC3, 1.0 / 255.0);
73+
74+
imFloat = (imFloat - 0.5) / 0.5;
75+
76+
return imFloat;
77+
}
78+
79+
/// <summary>
80+
/// Extracts image data into a float array in CHW (Channel, Height, Width) format
81+
/// required by PaddlePaddle
82+
/// </summary>
83+
/// <param name="src">Source image with 3 channels</param>
84+
/// <returns>Float array containing image data in CHW format</returns>
85+
private static float[] ExtractMat(Mat src)
86+
{
87+
int rows = src.Rows;
88+
int cols = src.Cols;
89+
float[] result = new float[rows * cols * 3];
90+
GCHandle resultHandle = default;
91+
try
92+
{
93+
resultHandle = GCHandle.Alloc(result, GCHandleType.Pinned);
94+
IntPtr resultPtr = resultHandle.AddrOfPinnedObject();
95+
96+
// Extract each channel and store in CHW format
97+
for (int i = 0; i < src.Channels(); ++i)
98+
{
99+
// Convert BGR to RGB order
100+
int rgb = 2 - i;
101+
using Mat dest = new(rows, cols, MatType.CV_32FC1, resultPtr + i * rows * cols * sizeof(float));
102+
Cv2.ExtractChannel(src, dest, rgb);
103+
}
104+
}
105+
finally
106+
{
107+
resultHandle.Free();
108+
}
109+
return result;
110+
}
111+
112+
/// <summary>
113+
/// Disposes the underlying PaddlePredictor
114+
/// </summary>
115+
public void Dispose()
116+
{
117+
_p.Dispose();
118+
}
119+
}
120+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<LangVersion>latest</LangVersion>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="OpenCvSharp4" Version="4.9.0.20240103" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\Sdcb.PaddleInference\Sdcb.PaddleInference.csproj" />
16+
</ItemGroup>
17+
18+
</Project>

src/Sdcb.PaddleSeg/SegModel.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Sdcb.PaddleInference;
2+
3+
namespace Sdcb.PaddleSeg
4+
{
5+
public abstract class SegModel : BaseModel
6+
{
7+
public static SegModel FromDirectory(string directoryPath) => new FileSegModel(directoryPath);
8+
9+
public override Action<PaddleConfig> DefaultDevice => PaddleDevice.Mkldnn();
10+
}
11+
}

src/Sdcb.PaddleSeg/paddleseg.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Sdcb.PaddleSeg Usage Example
2+
3+
This example demonstrates how to use Sdcb.PaddleSeg for image segmentation.
4+
5+
## Basic Usage
6+
7+
### 1. Import Required Namespaces
8+
9+
```csharp
10+
using OpenCvSharp;
11+
using Sdcb.PaddleInference;
12+
using Sdcb.PaddleSeg;
13+
```
14+
15+
### 2. Load Model
16+
17+
First, load the segmentation model from the model directory:
18+
19+
```csharp
20+
SegModel segModel = SegModel.FromDirectory(@"E:\gitproject\PaddleSeg\output\infer_model");
21+
```
22+
23+
### 3. Create Predictor
24+
25+
Create a predictor using the loaded model, here we use MKLDNN acceleration:
26+
27+
```csharp
28+
PaddleSegPredictor paddleSegPredictor = new PaddleSegPredictor(segModel, PaddleDevice.Mkldnn());
29+
```
30+
31+
### 4. Load Image and Perform Prediction
32+
33+
```csharp
34+
// Load input image
35+
Mat img = new Mat(@"E:\gitproject\PaddleSeg\datasets\optic_disc_seg\JPEGImages\H0002.jpg");
36+
37+
// Perform prediction
38+
var result = paddleSegPredictor.Run(img);
39+
```
40+
41+
### 5. Process Prediction Results
42+
43+
Convert the prediction results to a binary mask image:
44+
45+
```csharp
46+
// Create single-channel mask image
47+
Mat mask = new Mat(img.Size(), MatType.CV_8UC1);
48+
49+
// Convert segmentation result to mask
50+
int width = img.Width;
51+
int height = img.Height;
52+
for (int y = 0; y < height; y++)
53+
{
54+
for (int x = 0; x < width; x++)
55+
{
56+
int index = y * width + x;
57+
if (index < result.Length)
58+
{
59+
// Convert 1 to 255, keep 0 as 0
60+
mask.Set(y, x, result[index] == 1 ? (byte)255 : (byte)0);
61+
}
62+
}
63+
}
64+
65+
// Save mask image
66+
Cv2.ImWrite("mask.png", mask);
67+
```
68+
69+
## Notes
70+
71+
1. Ensure that `Sdcb.PaddleSeg`, `Sdcb.PaddleInference`, and `OpenCvSharp4` NuGet packages are properly installed
72+
2. Model path should point to a valid PaddleSeg exported model directory
73+
3. Input image path should be a valid image file path
74+
75+
## Results
76+
77+
The program will generate a binary mask image named `mask.png`, where:
78+
- White regions (255) represent the target area
79+
- Black regions (0) represent the background

0 commit comments

Comments
 (0)