Skip to content

Commit

Permalink
Nice results came (#11)
Browse files Browse the repository at this point in the history
* Nice results came

* Working MlPreprocessing with Preporcessing param
  • Loading branch information
artusiep authored Oct 26, 2021
1 parent cea6741 commit 79d9469
Show file tree
Hide file tree
Showing 16 changed files with 419 additions and 137 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
detector/configs/models/*
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ RUN apt-get update && \
COPY requirements.txt .
RUN pip install -r requirements.txt

ENV TF_CPP_MIN_LOG_LEVEL 3

COPY .dockerignore .
COPY detector_cli.py .
COPY detector/ detector

Expand Down
40 changes: 33 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
[![License: CC BY-NC 4.0](https://licensebuttons.net/l/by-nc/4.0/80x15.png)](https://creativecommons.org/licenses/by-nc/4.0/)
# PhotoVoltaicPanelDetection

This repository contains solution for automatic detection of solar modules.
This repository contains solution for automatic detection of solar modules. Main aim was
to prepare a tool that can be configured and based on that configuration produce detected
modules in to be defined formats, commonly used in machine learning but not only.

## Running in docker container

### Build Docker Image from source

```
docker build -t pvpd:0.0.1 -t pvpd .
```

Try running:
```
docker run pvpd:0.1.0 -h
```

### Example run from docker

```
docker run -v ${PWD}/data:/usr/data pvpd:0.0.1 -c PlasmaConfig -o /usr/data/ -f /usr/data/plasma/1.JPG -t raw -cm plasma
docker run -v ${PWD}/data:/usr/data pvpd:0.1.0 -c PlasmaConfig -o /usr/data --f /usr/data/raw/3.JPG -t raw -cm plasma -l LabelMeLabeler
```

- `-v ${PWD}/data:/usr/data` shares directory `data` from repository to `/usr/data`
- `pvpd:0.0.1` version of docker image
- `pvpd:0.1.0` version of docker image
- `-c PlasmaConfig` which config will be used for image annotation
- `-o /usr/data/` data will be saved in the shared directory `data`
- `-f /usr/data/plasma/1.JPG` file `1.JPG` is going to be analyzing
- `-t raw` file `1.JPG` is in a raw format. There is a need to extract thermal information
- `-cm plasma` which color map will be used for thermal image values

### Build Docker Image from source

```
docker build -t pvpd:0.0.1 -t pvpd .
```

## Local installation

Expand Down Expand Up @@ -52,3 +61,20 @@ pip install -r requirements.txt
```
./detector_cli.py
```

### Configs
Configs are parts of this project that every user adjust for private images.
All config can be found those in [config directory](detector/configs). Using them requires some
knowledge about classic image processing methods.

Whole detection is split into distinct steps that could be configured using each step
`Param`. It is highly suggested taking a look at those.

For now available Configs can be found using `./detector_cli.py -h`

### Labelers
Labeler is an entity that can be selected to generate a file containing data and metadata
from detection process. New labelers can be easily created to meet individual needs. All
labelers can be found in [labelers directory](detector/labelers)

For now available Labelers can be found using `./detector_cli.py -h`
Binary file added data/raw/1.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/raw/2.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/raw/3.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions detector/configs/plasma.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ class PlasmaConfig(Config):
edge_detector_params = EdgeDetectorParams(
hysteresis_min_thresh=35,
hysteresis_max_thresh=45,
kernel_size=(3, 3),
kernel_shape=cv2.MORPH_RECT,
kernel_size=(7, 7),
kernel_shape=cv2.MORPH_CROSS,
dilation_steps=4
)
segment_detector_params = SegmentDetectorParams(
d_rho=1,
d_theta=np.pi / 180,
min_num_votes=135,
min_line_length=max(floor(5 * (__edge_image_scaling)), 20),
min_num_votes=85,
min_line_length=max(floor(10 * (__edge_image_scaling-10)), 20),
max_line_gap=20 * __edge_image_scaling,
extension_pixels=35 * __edge_image_scaling
)
Expand All @@ -43,7 +43,7 @@ class PlasmaConfig(Config):
)
cluster_cleaning_params = ClusterCleaningParams(
max_angle_variation_mean=np.pi / 180 * 20,
max_merging_angle=np.pi / 180 * 30,
max_merging_angle=np.pi / 180 * 40,
max_endpoint_distance=10
)
intersection_detector_params = IntersectionDetectorParams(
Expand Down
13 changes: 6 additions & 7 deletions detector/configs/plasma_ml.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@
from detector.detection import EdgeDetectorParams, SegmentDetectorParams, SegmentClustererParams, \
ClusterCleaningParams, IntersectionDetectorParams, RectangleDetectorParams
from detector.detection.preprocessing_ml import PreprocessingMlParams
from trainer.utils.consts import UNET_6_LAYERS


class PlasmaMlConfig(Config):
__preprocessing_image_scaling = 1
__edge_image_scaling = 3
preprocessing_params = PreprocessingMlParams(
model_name=UNET_6_LAYERS,
weight_path='/Users/artursiepietwoski/Developer/Private/PhotoVoltaicPanelsDetection/neural/trainer/training_result/1_training_unet_6_layers_2021-09-28T00:23:27_gray/cp.ckpt',
model_name='unet_6_layers',
weight_path='detector/configs/models/content/training_result/1_training_unet_6_layers_2021-09-28T00:23:27_gray/cp.ckpt',
gray=True,
model_image_size=(128, 128),
model_image_size=(192,192),
start_neurons=16,
gaussian_blur=3,
model_output_threshold=64,
Expand All @@ -27,14 +26,14 @@ class PlasmaMlConfig(Config):
image_scaling=__edge_image_scaling,
hysteresis_min_thresh=35,
hysteresis_max_thresh=45,
kernel_size=(3, 3),
kernel_shape=cv2.MORPH_RECT,
kernel_size=(7, 7),
kernel_shape=cv2.MORPH_CROSS,
dilation_steps=4
)
segment_detector_params = SegmentDetectorParams(
d_rho=1,
d_theta=np.pi / 180,
min_num_votes=110,
min_num_votes=75,
min_line_length=max(floor(10 * (__edge_image_scaling - 2)), 20),
max_line_gap=20 * __edge_image_scaling,
extension_pixels=35 * __edge_image_scaling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@
from detector.detection import EdgeDetectorParams, SegmentDetectorParams, SegmentClustererParams, \
ClusterCleaningParams, IntersectionDetectorParams, RectangleDetectorParams
from detector.detection.preprocessing_ml import PreprocessingMlParams
from trainer.utils.consts import UNET_6_LAYERS, UNET_4_LAYERS


class PlasmaMl4Unet256Config(Config):
class PlasmaMl4Unet384Config(Config):
__preprocessing_image_scaling = 1
__edge_image_scaling = 3
preprocessing_params = PreprocessingMlParams(
model_name=UNET_4_LAYERS,
weight_path='detector/configs/models/1_training_unet_4_layers_2021-10-17T23:28:47_gray/cp.ckpt',
model_name='unet_4_layers',
weight_path='detector/configs/models/content/training_result/1_training_unet_4_layers_2021-10-17T23:28:47_gray/cp.ckpt',
gray=True,
model_image_size=(256, 256),
model_image_size=(384, 384),
start_neurons=16,
gaussian_blur=3,
model_output_threshold=64,
Expand All @@ -27,14 +26,14 @@ class PlasmaMl4Unet256Config(Config):
image_scaling=__edge_image_scaling,
hysteresis_min_thresh=35,
hysteresis_max_thresh=45,
kernel_size=(3, 3),
kernel_shape=cv2.MORPH_RECT,
kernel_size=(7, 7),
kernel_shape=cv2.MORPH_CROSS,
dilation_steps=4
)
segment_detector_params = SegmentDetectorParams(
d_rho=1,
d_theta=np.pi / 180,
min_num_votes=110,
min_num_votes=75,
min_line_length=max(floor(10 * (__edge_image_scaling - 2)), 20),
max_line_gap=20 * __edge_image_scaling,
extension_pixels=35 * __edge_image_scaling
Expand All @@ -49,14 +48,14 @@ class PlasmaMl4Unet256Config(Config):
)
cluster_cleaning_params = ClusterCleaningParams(
max_angle_variation_mean=np.pi / 180 * 20,
max_merging_angle=np.pi / 180 * 40,
max_endpoint_distance=15
max_merging_angle=np.pi / 180 * 30,
max_endpoint_distance=10
)
intersection_detector_params = IntersectionDetectorParams(
angle_threshold=np.pi / 180 * 25
)
rectangle_detector_params = RectangleDetectorParams(
aspect_ratio=1.5,
aspect_ratio_relative_deviation=0.35,
min_area=20 * 40
min_area=20 * 30
)
54 changes: 29 additions & 25 deletions detector/detection/edge_detection.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import logging
from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Tuple

import cv2
import numpy as np

from detector.utils.display import display_image_in_actual_size
from detector.utils.images import scale_image


Expand All @@ -28,13 +29,8 @@ class EdgeDetectorParams:

kernel_size: Tuple[int, int] = (3, 3)
kernel_shape: int = cv2.MORPH_CROSS
kernel: None = field(init=False, default_factory=tuple)

dilation_steps: int = 4

def __post_init__(self):
self.kernel = cv2.getStructuringElement(self.kernel_shape, self.kernel_size)


class EdgeDetector:
"""Class responsible for detecting edges in greyscale images.
Expand Down Expand Up @@ -87,14 +83,35 @@ def strengthen_edges(self, input_image):
# adaptive thresholding
adaptive_threshold = cv2.adaptiveThreshold(blurred_image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
denoised_image = cv2.fastNlMeansDenoising(adaptive_threshold, 9, 21, 7) # 30, 7, 25
denoised_image = cv2.fastNlMeansDenoising(adaptive_threshold, 11, 31, 9) # 30, 7, 25
# contrasting
brightness = 0
contrast = 64 # contrast_const

contrasted_image = self.apply_brightness_contrast(denoised_image, brightness, contrast)
return contrasted_image

@staticmethod
def skeletize(dilated, kernel_shape, kernel_size):
done = False
size = np.size(dilated)
skel = np.zeros(dilated.shape, np.uint8)
img = dilated

kernel = cv2.getStructuringElement(kernel_shape, kernel_size)
while not done:
logging.debug("Eroding canny edges")
eroded = cv2.erode(img, kernel)
temp = cv2.dilate(eroded, kernel)
temp = cv2.subtract(img, temp)
skel = cv2.bitwise_or(skel, temp)
img = eroded.copy()

zeros = size - cv2.countNonZero(img)
if zeros == size:
done = True
return skel

def detect(self) -> None:
"""Detects the edges in the image passed to the constructor using the parameters passed to the constructor.
"""
Expand All @@ -113,25 +130,12 @@ def detect(self) -> None:
dilated = cv2.dilate(canny, (3, 3), iterations=self.params.dilation_steps)
logging.debug("Dilate canny edges with {} steps".format(self.params.dilation_steps))

size = np.size(dilated)
skel = np.zeros(dilated.shape, np.uint8)

img = dilated
done = False

kernel_size = (7, 7)
kernel_shape = cv2.MORPH_CROSS
kernel = cv2.getStructuringElement(kernel_shape, kernel_size)
while not done:
logging.debug("Eroding canny edges")
eroded = cv2.erode(img, kernel)
temp = cv2.dilate(eroded, kernel)
temp = cv2.subtract(img, temp)
skel = cv2.bitwise_or(skel, temp)
img = eroded.copy()
kernel_shape = self.params.kernel_shape
kernel_size = self.params.kernel_size

zeros = size - cv2.countNonZero(img)
if zeros == size:
done = True
skel1 = self.skeletize(dilated, kernel_shape, (3,3))
skel2 = self.skeletize(dilated, kernel_shape, (5,5))

skel = cv2.bitwise_or(skel1, skel2)
self.edge_image = skel
Loading

0 comments on commit 79d9469

Please sign in to comment.