Title: | Classifies Image Pixels by Colour |
---|---|
Description: | Contains functions to classify the pixels of an image file (jpeg or tiff) by its colour. It implements a simple form of the techniques known as Support Vector Machine adapted to this particular problem. |
Authors: | Carlos Real [aut, cre] |
Maintainer: | Carlos Real <[email protected]> |
License: | GPL-3 |
Version: | 1.0.0 |
Built: | 2024-08-29 22:57:00 UTC |
Source: | https://github.com/ropensci/pixelclasser |
Classifies the pixels represented in an object of class
transformed_image
using the rules contained in a list of objects of
class pixel_cat
.
classify_pixels(image_prop, ..., unclassed_colour = "black", verbose = TRUE)
classify_pixels(image_prop, ..., unclassed_colour = "black", verbose = TRUE)
image_prop |
an array containing the image. It must be an
object produced with function |
... |
a list of objects of class |
unclassed_colour |
a character string setting the colour to be assigned to unclassified pixels. Defaults to "black". |
verbose |
a logical value. When TRUE (default) the function prints some statistics about the classification. |
This function generates a set of incidence matrices indicating
whether a pixel belongs to a pixel category or not. An additional matrix
identifies the pixels that do not belong to the defined categories, i e
unclassed pixels. Depending on how the rules were defined, it can be void
or contain pixels, but it is always present and named unclassified
.
To create the incidence matrices for each category, a matrix for each rule
is created and then combined with the matrices of the other using the
and
operator.
When a set of subcategories is used, the procedure is the same for each
subcategory and then the matrices of the subcategories are combined again,
this time using the or
operator. See the help for
define_subcat
for more details.
unclassed_colour
can be specified in any form understood by
grDevices::col2grb
.
Returns an object of class classified_image
, which is a list
containing nested lists. Each first-level element corresponds to one of the
pixel categories and its name is the category name. They contains the
second-level list, which have the following elements:
colour
: a matrix defining a colour to paint the pixels in the
classified image. Inherited from the pixel_class
object defining the
class.
incid_mat
: a logical matrix where TRUE
values indicate
that the pixel belongs to this pixel category.
# The series of steps to classify a image supplied in the package yellow <- "#ffcd0eff" blue <- "#5536ffff" ivy_oak_rgb <- read_image(system.file("extdata", "IvyOak400x300.JPG", package = "pixelclasser")) rule_01 <- define_rule("rule_01", "g", "b", list(c(0.345, 1/3), c(0.40, 0.10)), comp_op = "<") rule_02 <- define_rule("rule_02", "g", "b", list(c(0.345, 1/3), c(0.40, 0.10)), comp_op = ">=") cat_dead_leaves <- define_cat("dead_leaves", blue, rule_01) cat_living_leaves <- define_cat("living_leaves", yellow, rule_02) ivy_oak_classified <- classify_pixels(ivy_oak_rgb, cat_dead_leaves, cat_living_leaves)
# The series of steps to classify a image supplied in the package yellow <- "#ffcd0eff" blue <- "#5536ffff" ivy_oak_rgb <- read_image(system.file("extdata", "IvyOak400x300.JPG", package = "pixelclasser")) rule_01 <- define_rule("rule_01", "g", "b", list(c(0.345, 1/3), c(0.40, 0.10)), comp_op = "<") rule_02 <- define_rule("rule_02", "g", "b", list(c(0.345, 1/3), c(0.40, 0.10)), comp_op = ">=") cat_dead_leaves <- define_cat("dead_leaves", blue, rule_01) cat_living_leaves <- define_cat("living_leaves", yellow, rule_02) ivy_oak_classified <- classify_pixels(ivy_oak_rgb, cat_dead_leaves, cat_living_leaves)
Creates an object of class pixel_cat
, which contains a list of objects
of class pixel_subcat
.
define_cat(cat_name, cat_colour, ...)
define_cat(cat_name, cat_colour, ...)
cat_name |
a character string containing the name of the category. |
cat_colour |
a character string defining the colour to paint the pixels with when creating a classified picture. |
... |
a list of |
The function receives a list of objects of class pixel_subcat
and creates a list of class pixel_cat
with them. However, for cases
that does not need subcategories, i e that only need a set of rules,need a
single set of rules, these can be passed to the function, which creates an
internal subcategory object to contain them. See the examples below.
Note that it is an error to pass a mixture of pixel_rule
and
pixel_subcat
objects.
colour
can be specified in any form understood by
grDevices::col2grb
.
A list of class pixel_cat
with the following elements:
name
: a character string containing the name of the
pixel category.
colour
: a character string describing the
colour of the pixels of the category in the classified images.
subcats
: a list containing the subcategories. Their names are the
names of the elements of the list.
define_rule
, define_subcat
,
col2rgb
# The rules are not consistent, they are only useful as examples rule01 <- define_rule("R01", "g", "b", list(c(0.35, 0.30), c(0.45, 0.10)), ">=") rule02 <- define_rule("R02", "g", "b", list(c(0.35, 0.253), c(0.45, 0.253)), ">=") rule03 <- define_rule("R03", "g", "b", list(c(0.35, 0.29), c(0.49, 0.178)), ">=") rule04 <- define_rule("R04", "g", "b", list(c(0.35, 0.253), c(0.45, 0.253)), "<") subcat01 <- define_subcat("Subcat01", rule01, rule02) subcat02 <- define_subcat("Subcat02", rule03, rule04) cat01 <- define_cat("Cat01", "#ffae2a", subcat01, subcat02) # A single category defined by a set of rules, not subcategories cat02 <- define_cat("Cat02", "#00ae2a", rule01, rule02, rule03)
# The rules are not consistent, they are only useful as examples rule01 <- define_rule("R01", "g", "b", list(c(0.35, 0.30), c(0.45, 0.10)), ">=") rule02 <- define_rule("R02", "g", "b", list(c(0.35, 0.253), c(0.45, 0.253)), ">=") rule03 <- define_rule("R03", "g", "b", list(c(0.35, 0.29), c(0.49, 0.178)), ">=") rule04 <- define_rule("R04", "g", "b", list(c(0.35, 0.253), c(0.45, 0.253)), "<") subcat01 <- define_subcat("Subcat01", rule01, rule02) subcat02 <- define_subcat("Subcat02", rule03, rule04) cat01 <- define_cat("Cat01", "#ffae2a", subcat01, subcat02) # A single category defined by a set of rules, not subcategories cat02 <- define_cat("Cat02", "#00ae2a", rule01, rule02, rule03)
Creates an object of class pixel_rule
from a line in rgb
space,
defined by the user, and a relational operator.
define_rule(rule_name, x_axis, y_axis, rule_points, comp_op)
define_rule(rule_name, x_axis, y_axis, rule_points, comp_op)
rule_name |
a character string containing the name of the rule. |
x_axis |
a character string selecting the colour variable used as x
axis, one of |
y_axis |
a character string selecting the colour variable used as y
axis, one of |
rule_points |
either an object of of class |
comp_op |
a character string containing one of the comparison operators
|
This function estimates the slope (a
) and intercept
(c
) of the line y = ax + c
using the coordinates of two
points on the line. x
and y
are two colour variables selected
by the user (r
, g
, or b
). The line divides the plane
in two subsets and the comparison operator selects the subset that contains
the points (pixels) of interest.
When a list of two points is passed in rule_points
, it is internally
converted into an an object of class rule_points
.
The pair of points used to define the line are not constrained to belong to
the area occupied by the pixels, but they are used by plot_rule()
as
the start and end of the plotted line. Therefore, the extremes of the line
can be selected in the most convenient way, provided that the line divides
correctly the categories. Convenience means that the line should seem nice
in the plot, if this matters.
Because the variables were transformed into proportions, the pixel are
always inside the triangle defined by the points (0, 0), (1, 0), (0,
1)
. So, the sides of this triangle can be considered as implicit rules
which do not need to be created. In this way, a single line creates two
polygons by cutting the triangle in two. The implicit rules can reduce the
number of rules to create in most cases.
A list of class pixel_rule
containing the following elements:
rule_name
: a character string containing the rule name.
rule_text
: a character string containing the mathematical
expression of the rule.
comp_op
: a character string containing the comparison operator
used in the rule.
a
: a numerical vector containing the parameter a
(slope) of the line.
c
: a numerical vector containing the parameter c
(intercept) of the line.
x_axis
: a character string containing the colour variable
selected as x
axis.
y_axis
: a character string containing the colour variable
selected as y
axis.
first_point
: a numerical vector containing the coordinates of
the first point used to estimate the line equation.
second_point
: a numerical vector containing the coordinates of
the second point.
define_subcat
, define_cat
,
plot_rule
, plot_rgb_plane
# Creating the line by passing the coordinates of two points on the line: rule01 <- define_rule("rule01", "g", "b", list(c(0.35, 0.30), c(0.45, 0.10)),">") # A vertical line as a rule; note that the equation is simplified rule02 <- define_rule("rule02", "g", "b", list(c(0.35, 0.30), c(0.35, 0.00)), ">") ## Not run: # Creating the rule by passing an object of type rule_point: rule_points01 <- place_rule("g", "b") rule03 <- define_rule("rule03", "g", "b", rule_points01,">") # Note that the creation of the intermediate object can be avoided: rule04 <- define_rule("rule04", "g", "b", place_rule("g", "b"),">") ## End(Not run)
# Creating the line by passing the coordinates of two points on the line: rule01 <- define_rule("rule01", "g", "b", list(c(0.35, 0.30), c(0.45, 0.10)),">") # A vertical line as a rule; note that the equation is simplified rule02 <- define_rule("rule02", "g", "b", list(c(0.35, 0.30), c(0.35, 0.00)), ">") ## Not run: # Creating the rule by passing an object of type rule_point: rule_points01 <- place_rule("g", "b") rule03 <- define_rule("rule03", "g", "b", rule_points01,">") # Note that the creation of the intermediate object can be avoided: rule04 <- define_rule("rule04", "g", "b", place_rule("g", "b"),">") ## End(Not run)
Creates an object of class pixel_subcat
from a list of objects of
class pixel_rule
.
define_subcat(subcat_name, ...)
define_subcat(subcat_name, ...)
subcat_name |
a character string containing the name of the subcategory. |
... |
a list of objects of class |
When the shape of the cluster of pixels belonging to one category is not convex, the rules become inconsistent (the lines cross in awkward ways) and the classification produced is erroneous. To solve this problem, the complete set of rules is divided into several subsets (subcategories) that break the original non-convex shape into a set of convex polygons. Note that any polygon can be divided in a number of triangles, so this problem always has solution. However, in many cases (such as the one presented in the pixelclasser vignette) a complete triangulation is not needed.
Internally, classify_pixels()
classifies the points belonging to
each subcategory and then joins the incidence matrices using the or
operator, to create the matrix for the whole category.
An object of class pixel_subcat
, which is a list with the
following elements:
name
a character string containing the name of the
subcategory.
rules_list
a list of pixel_rule
objects.
rule01 <- define_rule("R01", "g", "b", list(c(0.35, 0.30), c(0.45, 0.10)), ">=") rule02 <- define_rule("R02", "g", "b", list(c(0.35, 0.253), c(0.45, 0.253)), ">=") subcat01 <- define_subcat("Subcat_01", rule01, rule02)
rule01 <- define_rule("R01", "g", "b", list(c(0.35, 0.30), c(0.45, 0.10)), ">=") rule02 <- define_rule("R02", "g", "b", list(c(0.35, 0.253), c(0.45, 0.253)), ">=") subcat01 <- define_subcat("Subcat_01", rule01, rule02)
This function adds a label to the line that represents a rule on a plot
created by plot_rgb_plane()
.
label_rule(rule, label = "", shift = c(0, 0), ...)
label_rule(rule, label = "", shift = c(0, 0), ...)
rule |
an object of class |
label |
a string to label the line. It is attached at the coordinates of the start (first point) of the line. |
shift |
a numeric vector to set the displacement of the label from the start of the line. Expressed in graph units, defaults to c(0, 0). |
... |
additional graphical parameters passed to the underlying
|
The function uses the information stored in the pixel_rule object to
plot the label at the start of the line. The shift
values, expressed
in plot coordinates, are added to the coordinates of that point to place
the label elsewhere. Note that ... can be used to pass values for the
adj
parameter to the underlying graphics::text()
function,
which also modifies the position of the label.
Use a character string understood by grDevices::col2rgb()
to set
the colour of the label.
The function does not return any value.
plot_rgb_plane
, define_rule
,
col2rgb
, text
rule_01 <- define_rule("rule_01", "g", "b", list(c(0.1, 0.8), c(0.40, 0.10)), "<") plot_rgb_plane("g", "b") # The rule is represented as a green line plot_rule(rule_01, col = "green") # And the label is added in three different positions by passing col and adj # to the underlying function label_rule(rule_01, label = expression('R'[1]*''), shift = c(0,0), col = 'black', adj = 1.5) label_rule(rule_01, label = expression('R'[1]*''), shift = c(0.2, -0.4), col = 'blue', adj = 0) label_rule(rule_01, label = expression('R'[1]*''), shift = c(0.3, -0.7), col = 'black', adj = -0.5)
rule_01 <- define_rule("rule_01", "g", "b", list(c(0.1, 0.8), c(0.40, 0.10)), "<") plot_rgb_plane("g", "b") # The rule is represented as a green line plot_rule(rule_01, col = "green") # And the label is added in three different positions by passing col and adj # to the underlying function label_rule(rule_01, label = expression('R'[1]*''), shift = c(0,0), col = 'black', adj = 1.5) label_rule(rule_01, label = expression('R'[1]*''), shift = c(0.2, -0.4), col = 'blue', adj = 0) label_rule(rule_01, label = expression('R'[1]*''), shift = c(0.3, -0.7), col = 'black', adj = -0.5)
pixelclasser
contains functions to classify the pixels of an image
file (in format jpeg or tiff) by its colour. It uses a simple form of the
technique known as Support Vector Machine, adapted to this particular
problem. The original colour variables (R, G, B
) are transformed into
colour proportions (r, g, b
), and the resulting two dimensional plane,
defined by any convenient pair of the transformed variables is divided in
several subsets (categories) by one or more straight lines (rules) selected
by the user. Finally, the pixels belonging to each category are identified
using the rules, and a classified image can be created and saved.
To classify the pixels of an image, a series of steps must be done in the following order, using the functions shown in parenthesis:
import the image into an R array of transformed (rgb
) data
(read_image()
).
plot the pixels of the image on the plane of two transformed variables
that shows the categories of pixels most clearly (plot_rgb_plane()
,
plot_pixels
).
trace lines between the pixel clusters and use them to create
classification rules (place_rule()
, define_rule
,
plot_rule()
).
combine the rules to define categories. Sometimes the rules are
combined into subcategories and these into categories (define_cat()
,
define_subcat()
).
use the categories to classify the pixels (classify_pixels()
).
save the results of the classification as an image, if needed
(save_clasif_image()
).
These steps are explained in depth in the vignette included in the package.
Carlos Real ([email protected])
A wrapper function for graphics::locator
that makes the creation
of rules easier.
place_rule(x_axis, y_axis, line_type = "f")
place_rule(x_axis, y_axis, line_type = "f")
x_axis |
a character string indicating the colour variable that
corresponds to the x axis, one of |
y_axis |
a character string indicating the colour variable that
corresponds to the y axis, one of |
line_type |
a character string indicating that the line is vertical
|
This function calls graphics::locator
allowing to select two
points, plots the line joining these points and returns a list
containing their coordinates. The coordinates are rearranged to
pass them to define_rule()
.
True horizontal and vertical lines are difficult to create by hand. In
these cases, specifying "vertical"
or "horizontal"
(partial
match allowed, i e "h") will copy the appropriate coordinate value from the
first point to the second. Note that this is done after locator()
returns, so the plot will show the line joining the original points, not
the corrected ones. Use plot_rule()
to see corrected line.
A list of class rule_points
containing the following elements:
x_axis
: a character string containing the colour variable
selected as x
axis.
y_axis
: a character string containing the colour variable
selected as y
axis.
first_point
: coordinates of the start point of the line.
second_point
: coordinates of the end point of the line.
locator
, define_rule
,
plot_rule
, plot_rgb_plane
## Not run: plot_rgb_plane("r", "g") line01 <- place_rule("r", "g") # A "free" line line02 <- place_rule("r", "g", "h") # A horizontal line ## End(Not run)
## Not run: plot_rgb_plane("r", "g") line01 <- place_rule("r", "g") # A "free" line line02 <- place_rule("r", "g", "h") # A horizontal line ## End(Not run)
This function is a wrapper for function points()
in package
graphics
for plotting the pixels of a transformed rgb image on the
triangular diagram previously created by plot_rgb_plane()
.
plot_pixels(image_rgb, x_axis, y_axis, ...)
plot_pixels(image_rgb, x_axis, y_axis, ...)
image_rgb |
an object produced by |
x_axis |
a character string indicating which colour variable use as x. |
y_axis |
a character string indicating which colour variable use as y. |
... |
additional graphical parameters, mainly to set the colour
( |
It is advantageous to specify a colour such as "#00000005"
which is black but almost transparent. In this way a kind of density plot
is created because the clustering of points creates areas of darker colour.
Note that a colour without specific transparency information defaults to an
opaque colour, so "#000000"
is the same as "#000000ff"
. The
colours can be specified in any form understandable by
grDevices::col2rgb
, but the hexadecimal string allows setting the
colour transparency and it is the preferred style. Note also that the
points are plotted using pch = ".", as any other symbol would clutter the
graph.
Warning: plotting several million points in an R graph is a slow process. Be patient or reduce the size of the images as much as possible. Having a nice smartphone with a petapixel camera sensor is good for artistic purposes, but not always for efficient scientific work.
The function does not return any value.
# Plotting the pixels of the example image included in this package ivy_oak_rgb <- read_image(system.file("extdata", "IvyOak400x300.JPG", package = "pixelclasser")) plot_rgb_plane("g", "b") plot_pixels(ivy_oak_rgb, "g", "b", col = "#00000005")
# Plotting the pixels of the example image included in this package ivy_oak_rgb <- read_image(system.file("extdata", "IvyOak400x300.JPG", package = "pixelclasser")) plot_rgb_plane("g", "b") plot_pixels(ivy_oak_rgb, "g", "b", col = "#00000005")
Plots a plane of the two variables selected by the user (r
, g
or b
) and, to serve as visual references, the lines limiting the
triangular area that can contain pixels (in blue) and the areas where one of
the colour variables has the larger proportion values (in red). Points
representing the pixels of a transformed image and lines representing the
rules can be later added to the plot using functions plot_pixels()
and
plot_rule()
.
plot_rgb_plane( x_axis, y_axis, plot_limits = TRUE, plot_guides = TRUE, plot_grid = TRUE, ... )
plot_rgb_plane( x_axis, y_axis, plot_limits = TRUE, plot_guides = TRUE, plot_grid = TRUE, ... )
x_axis |
a character string indicating which colour variable use as x. |
y_axis |
a character string indicating which colour variable use as y. |
plot_limits |
a logical value. When TRUE (default) the limits of the area where the pixels can be found are plotted. |
plot_guides |
a logical value. When TRUE (default) the limits of the area where one variable dominates are plotted. |
plot_grid |
a logical value; if TRUE (default) a grid is added. |
... |
allows passing graphical parameters to the plotting functions. |
Graphical parameters can be passed to the function to modify the
appearance of the plot. Intended for passing xlim
and ylim
values to plot the part of the graph where the points are concentrated.
Because the variables were transformed into proportions, the pixel are
always inside the triangle defined by the points (0, 0), (1, 0), (0,
1)
. This triangle is plotted in blue. The point where all three variables
have the same value is (1/3, 1/3)
. The lines joining this point with
the centers of the triangle sides divide the areas where one of the three
variables has higher proportions than the other two. These lines are
plotted as visual aids, so they can be deleted at will.
The function does not return any value.
plot_pixels
, plot_rule
,
define_rule
# Simplest call plot_rgb_plane("g", "b") # Plane without the red lines plot_rgb_plane("g", "b", plot_guides = FALSE) # Restricting the plane area to show plot_rgb_plane("g", "b", xlim = c(0.2, 0.5), ylim = c(0.0, 0.33))
# Simplest call plot_rgb_plane("g", "b") # Plane without the red lines plot_rgb_plane("g", "b", plot_guides = FALSE) # Restricting the plane area to show plot_rgb_plane("g", "b", xlim = c(0.2, 0.5), ylim = c(0.0, 0.33))
This function draws the line that defines a rule on the plot created by
plot_rgb_plane()
.
plot_rule(rule, label = "", ...)
plot_rule(rule, label = "", ...)
rule |
an object of class |
label |
a string to label the line. It is attached at the coordinates of the second point used to define the line. |
... |
additional graphical parameters passed to the underlying
|
The function uses the information stored in the pixel_rule
object
to plot the line.
Use the ... to set the colour and other characteristics of the line. Use
any character string understood by col2rgb()
.
Labels can be added to the rule using label_rule()
.
The function does not return any value.
plot_rgb_plane
, define_rule
,
label_rule
col2rgb
rule_01 <- define_rule("rule_01", "g", "b", list(c(0.345, 1/3), c(0.40, 0.10)), "<") plot_rgb_plane("g", "b") plot_rule(rule_01, col = "green")
rule_01 <- define_rule("rule_01", "g", "b", list(c(0.345, 1/3), c(0.40, 0.10)), "<") plot_rgb_plane("g", "b") plot_rule(rule_01, col = "green")
Imports an image file (in JPEG or TIFF format) into an array, and converts
the original R
, G
and B
values in the file into
proportions (r
, g
and b
variables).
read_image(file_name)
read_image(file_name)
file_name |
A character string containing the name of the image file. |
This function calls the functions readJPEG()
and
readTIFF()
in packages jpeg
and tiff
to import the
data into an R array. Then it transforms the data into proportions
Returns an array of dimensions r x c x 3
and class
transformed_image
, being r
and c
the number of rows
and columns in the image. The last dimension corresponds to the R
,
G
and B
variables (= bands) that define the colours of the
pixels. The values in the array are the proportions
of each colour (r, g, b
), i.e. r
= R
/ (R + G +
B
), and so on.
For more information about jpeg and tiff file formats, see the help
pages of readJPEG
and readTIFF
functions in packages jpeg
and tiff
, respectively.
# An example that loads the example file included in the package ivy_oak_rgb <- read_image(system.file("extdata", "IvyOak400x300.JPG", package = "pixelclasser"))
# An example that loads the example file included in the package ivy_oak_rgb <- read_image(system.file("extdata", "IvyOak400x300.JPG", package = "pixelclasser"))
Creates an image file in TIFF or JPEG format from an array of class
classified_image
.
save_classif_image(classified_image, file_name, ...)
save_classif_image(classified_image, file_name, ...)
classified_image |
an object of class |
file_name |
a character string with the name of the output file, including the extension. |
... |
further parameters to pass to functions |
The type of the output file (jpeg or tiff) is selected from the
extension included in the file name. It must be one of ("jpg", "JPG",
"jpeg", "JPEG", "tif", "TIF", "tiff", "TIFF")
.
Note that the default value for jpg quality is 0.7. For maximal quality set
quality = 1
using the ... argument. Such adjustments are not
needed with tiff
files, as this is a lossless format.
It does not return anything, only creates the file.
For more information about the options for file formatting see see the help
pages of readJPEG
and readTIFF
functions in packages jpeg
and tiff
, respectively.
## Not run: # Saving an hypothetical image. Note the use of quality to set the # maximum quality level in the JPEG file save_classif_image(image01_class, "./myimages/image01_classified.jpg", quality = 1) ## End(Not run)
## Not run: # Saving an hypothetical image. Note the use of quality to set the # maximum quality level in the JPEG file save_classif_image(image01_class, "./myimages/image01_classified.jpg", quality = 1) ## End(Not run)