Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
GGCE
GGCE Web
Commits
89bfd83b
Commit
89bfd83b
authored
Apr 13, 2020
by
Matija Obreza
Browse files
Detect and apply primary colors
parent
fc569627
Changes
2
Hide whitespace changes
Inline
Side-by-side
packages/counter/demo/ProcessImage.tsx
View file @
89bfd83b
import
*
as
React
from
'
react
'
;
import
{
withStyles
}
from
'
@material-ui/core/styles
'
;
import
{
count
}
from
'
@gringlobal/counter/counter
'
;
import
{
logElapsed
,
filterSobel
,
filterBlur
,
filterSum
,
filterSharpenWithMask
,
filter
Outline
}
from
'
@gringlobal/counter/filters
'
;
import
{
logElapsed
,
colorMainsClassifier
,
filter
Blur
}
from
'
@gringlobal/counter/filters
'
;
const
styles
=
{
canvases
:
{
...
...
@@ -25,8 +25,9 @@ const styles = {
resultOverlay
:
{
width
:
'
100%
'
,
minHeight
:
'
20px
'
,
position
:
'
absolute
'
as
'
absolute
'
,
//
position: 'absolute' as 'absolute',
border
:
'
solid 1px red
'
,
backgroundColor
:
'
lime
'
,
top
:
-
1
,
left
:
-
1
,
},
...
...
@@ -40,9 +41,13 @@ const styles = {
},
};
const
L
=
console
.
log
;
class
ProcessImage
extends
React
.
Component
<
any
>
{
// private MAX_PROCESSING_WIDTH: number = 300;
private
logger
:
any
;
private
loggerDate
:
number
=
Date
.
now
();
private
original
:
any
;
private
originalVideo
:
any
;
private
resultOverlay
:
any
;
...
...
@@ -50,6 +55,7 @@ class ProcessImage extends React.Component<any> {
public
constructor
(
props
)
{
super
(
props
);
this
.
logger
=
React
.
createRef
();
this
.
original
=
React
.
createRef
();
this
.
originalVideo
=
React
.
createRef
();
this
.
resultOverlay
=
React
.
createRef
();
...
...
@@ -61,6 +67,25 @@ class ProcessImage extends React.Component<any> {
video
:
false
,
};
public
componentDidMount
()
{
console
.
log
(
'
Component did mount
'
,
this
.
logger
);
if
(
this
.
logger
.
current
&&
this
.
logger
.
current
.
appendChild
)
{
console
.
log
=
(
text
,
...
args
)
=>
{
const
time
=
Date
.
now
();
L
(
`+
${
time
-
this
.
loggerDate
}
${
text
}
`
,
...
args
);
const
msg
=
document
.
createElement
(
'
pre
'
);
msg
.
innerHTML
=
`+
${
time
-
this
.
loggerDate
}
${
text
}
`
;
// \n${args.map((arg) => `${JSON.stringify(arg, null, 2).substring(0, 300)}\n\n`)}`;
this
.
logger
.
current
.
appendChild
(
msg
);
this
.
loggerDate
=
time
;
}
}
}
public
componentWillUnmount
()
{
console
.
log
=
L
;
L
(
'
Reset console.log
'
);
}
private
resizeCanvases
=
()
=>
{
const
source
=
this
.
state
.
video
?
this
.
originalVideo
.
current
:
this
.
original
.
current
;
console
.
log
(
`Source
${
this
.
state
.
video
?
'
Video
'
:
'
Image
'
}
size
${
this
.
state
.
video
?
source
.
videoWidth
:
source
.
width
}
x
${
this
.
state
.
video
?
source
.
videoHeight
:
source
.
height
}
`
);
...
...
@@ -79,6 +104,9 @@ class ProcessImage extends React.Component<any> {
}
private
captureImage
=
()
=>
{
if
(
this
.
logger
.
current
)
{
this
.
logger
.
current
.
innerHTML
=
''
;
}
const
source
=
this
.
state
.
video
?
this
.
originalVideo
.
current
:
this
.
original
.
current
;
const
contextSource
=
this
.
canvasSource
.
current
.
getContext
(
'
2d
'
);
contextSource
.
imageSmoothingEnabled
=
false
;
...
...
@@ -91,18 +119,20 @@ class ProcessImage extends React.Component<any> {
// startTime = logElapsed(startTime, 'filterBw');
// let filtered = filterSharpenWithMask(image);
// startTime = logElapsed(startTime, 'filterSharpen');
// filtered = colorClassifier(filtered, 16);
// startTime = logElapsed(startTime, 'colorClassifier');
let
filtered
=
filterBlur
(
image
);
startTime
=
logElapsed
(
startTime
,
'
filterBlur
'
);
filtered
=
filterSobel
(
filtered
);
startTime
=
logElapsed
(
startTime
,
'
filterSobel
'
);
filtered
=
filterSum
(
filtered
);
startTime
=
logElapsed
(
startTime
,
'
filterSum
'
);
filtered
=
filterSharpenWithMask
(
filtered
,
2
);
startTime
=
logElapsed
(
startTime
,
'
filterSharpenWithMask
'
);
filtered
=
filterOutline
(
filtered
);
startTime
=
logElapsed
(
startTime
,
'
filterOutline
'
);
filtered
=
colorMainsClassifier
(
filtered
,
2
);
startTime
=
logElapsed
(
startTime
,
'
colorMainsClassifier
'
);
// filtered = colorClassifier(filtered, 4);
// startTime = logElapsed(startTime, 'colorClassifier');
// filtered = filterSobel(filtered);
// startTime = logElapsed(startTime, 'filterSobel');
// filtered = filterTakeHighest(filtered);
// startTime = logElapsed(startTime, 'filterSum');
// filtered = filterSharpenWithMask(filtered, 2);
// startTime = logElapsed(startTime, 'filterSharpenWithMask');
// filtered = filterOutline(filtered);
// startTime = logElapsed(startTime, 'filterOutline');
// filtered = filterBW(filtered);
// startTime = logElapsed(startTime, 'filterBW');
// filtered = filterThreshold(filtered, 50);
...
...
@@ -181,11 +211,13 @@ class ProcessImage extends React.Component<any> {
</
div
>
}
<
h1
>
Count:
{
this
.
state
.
count
}
</
h1
>
<
div
className
=
{
classes
.
originalContainer
}
>
<
img
ref
=
{
this
.
original
}
onLoad
=
{
this
.
resizeCanvases
}
className
=
{
classes
.
original
}
src
=
"examples/G
3
2 seed.jpg"
style
=
{
{
display
:
this
.
state
.
video
?
'
none
'
:
''
}
}
/>
<
img
ref
=
{
this
.
original
}
onLoad
=
{
this
.
resizeCanvases
}
className
=
{
classes
.
original
}
src
=
"examples/G
2501
2 seed.jpg"
style
=
{
{
display
:
this
.
state
.
video
?
'
none
'
:
''
}
}
/>
<
video
ref
=
{
this
.
originalVideo
}
className
=
{
classes
.
original
}
onPlaying
=
{
this
.
resizeCanvases
}
style
=
{
{
display
:
this
.
state
.
video
?
''
:
'
none
'
}
}
></
video
>
<
canvas
ref
=
{
this
.
resultOverlay
}
className
=
{
classes
.
resultOverlay
}
></
canvas
>
</
div
>
<
button
onClick
=
{
this
.
toggleCamera
}
>
Camera
</
button
>
<
h1
>
Log
</
h1
>
<
div
ref
=
{
this
.
logger
}
></
div
>
<
h1
>
Debugger
</
h1
>
<
div
id
=
"canvases"
className
=
{
classes
.
canvases
}
/>
<
canvas
ref
=
{
this
.
canvasSource
}
className
=
{
classes
.
originalGraphics
}
></
canvas
>
...
...
packages/counter/src/filters.ts
View file @
89bfd83b
...
...
@@ -1000,6 +1000,282 @@ export function colorClassifier(image, buckets: number = 16): ImageData {
return
new
ImageData
(
dataR
,
w
,
h
);
}
/**
* Find color clusters
*
* @param image Input RGBA image
*/
export
function
colorMainsClassifier
(
image
,
buckets
:
number
=
1
):
ImageData
{
const
w
=
image
.
width
;
const
h
=
image
.
height
;
console
.
log
(
`colorMainsClassifier
${
w
}
x
${
h
}
buckets=
${
buckets
}
`
);
const
data
=
image
.
data
;
const
dataR
=
new
Uint8ClampedArray
(
w
*
h
*
4
);
let
primaries
=
new
Array
();
// for (let r = 0; r <= buckets; r++) {
// for (let g = 0; g <= buckets; g++) {
// for (let b = 0; b <= buckets; b++) {
// const v = new Vector(255 / buckets * r, 255 / buckets * g, 255 / buckets * b);
// primaries.push(v);
// }
// }
// }
primaries
.
push
(
new
Vector
(
0
,
0
,
0
));
primaries
.
push
(
new
Vector
(
255
,
255
,
255
));
primaries
.
push
(
new
Vector
(
128
,
128
,
128
));
// console.log(primaries);
const
colors
:
Map
<
number
,
any
>
=
new
Map
();
let
startTime
=
Date
.
now
();
for
(
let
y
=
0
;
y
<
h
;
y
++
)
{
for
(
let
x
=
0
;
x
<
w
;
x
++
)
{
const
p
=
(
y
*
w
+
x
)
*
4
;
const
R
=
data
[
p
+
0
];
const
G
=
data
[
p
+
1
];
const
B
=
data
[
p
+
2
];
// const A = data[p + 3];
const
color
=
R
*
256
*
256
+
G
*
256
+
B
;
const
colorCountAndIndex
=
colors
.
get
(
color
);
if
(
colorCountAndIndex
)
{
colorCountAndIndex
[
0
]
++
;
}
else
{
colors
.
set
(
color
,
[
1
,
-
1
]);
}
// const distances: Array<number> = primaries.map((primary) => primary.distance(R, G, B));
// let minIdx = 0, minVal = distances[0];
// for (let i = 1; i < primaries.length; i++) {
// if (distances[i] < minVal) {
// minIdx = i;
// minVal = distances[i];
// }
// }
// dataR[p + 0] = minIdx;
// dataR[p + 3] = 255;
// if (R === 3 && G === 2) {
// const v = new Vector(R, G, B);
// console.log('Vector', v.toString());
// const v2 = new Vector(dataR[p + 0], dataR[p + 1], dataR[p + 2]);
// console.log('Vector', v2.toString());
// }
}
}
startTime
=
logElapsed
(
startTime
,
`Found
${
colors
.
size
}
colors`
);
// let colorList = 0;
for
(
const
color
of
colors
.
keys
())
{
const
colorCountAndIndex
=
colors
.
get
(
color
);
// const B = color % 256;
// const G = (color >> 8) % 256;
// const R = (color >> 16) % 256;
// if (colorList++ < 1000) {
// console.log(`${R},${G},${B}`, colorCountAndIndex);
// }
if
(
colorCountAndIndex
[
0
]
<
4
)
{
colors
.
delete
(
color
);
}
}
logElapsed
(
startTime
,
`Keeping
${
colors
.
size
}
colors`
);
const
findPrimary
=
(
x
,
y
,
z
):
number
=>
{
let
minIdx
=
0
,
minVal
=
primaries
[
0
].
distance
(
x
,
y
,
z
);
for
(
let
i
=
1
;
i
<
primaries
.
length
;
i
++
)
{
const
d
=
primaries
[
i
].
distance
(
x
,
y
,
z
);
if
(
d
<
minVal
)
{
minIdx
=
i
;
minVal
=
d
;
}
}
return
minIdx
;
}
const
updateAverage
=
(
existingAvg
,
newAvg
)
=>
{
const
r
=
(
existingAvg
[
0
][
0
]
*
existingAvg
[
1
]
+
newAvg
[
0
][
0
]
*
newAvg
[
1
])
/
(
existingAvg
[
1
]
+
newAvg
[
1
]);
const
g
=
(
existingAvg
[
0
][
1
]
*
existingAvg
[
1
]
+
newAvg
[
0
][
1
]
*
newAvg
[
1
])
/
(
existingAvg
[
1
]
+
newAvg
[
1
]);
const
b
=
(
existingAvg
[
0
][
2
]
*
existingAvg
[
1
]
+
newAvg
[
0
][
2
]
*
newAvg
[
1
])
/
(
existingAvg
[
1
]
+
newAvg
[
1
]);
return
[[
r
,
g
,
b
],
existingAvg
[
1
]
+
newAvg
[
1
]
];
}
let
safety
=
0
;
let
clusterChanges
;
let
colorChanges
;
const
averagesByPrimary
:
Map
<
number
,
Array
<
any
>>
=
new
Map
();;
do
{
clusterChanges
=
0
;
colorChanges
=
0
;
averagesByPrimary
.
clear
();
for
(
const
color
of
colors
.
keys
())
{
const
colorCountAndIndex
=
colors
.
get
(
color
);
const
B
=
color
%
256
;
const
G
=
(
color
>>
8
)
%
256
;
const
R
=
(
color
>>
16
)
%
256
;
const
primaryIndex
=
findPrimary
(
R
,
G
,
B
);
if
(
colorCountAndIndex
[
1
]
!==
primaryIndex
)
{
colorCountAndIndex
[
1
]
=
primaryIndex
;
// colors.set(color, [ colorCountAndIndex[0], primaryIndex ]);
colorChanges
++
;
// if (safety >= 40) {
// console.log(`Yaay R${safety} ${R},${G},${B} from ${colorCountAndIndex[1]} -> ${primaryIndex}`);
// }
}
const
primaryAvg
=
averagesByPrimary
.
get
(
primaryIndex
)
||
[
[
R
,
G
,
B
],
0
];
// add to average
averagesByPrimary
.
set
(
primaryIndex
,
updateAverage
(
primaryAvg
,
[
[
R
,
G
,
B
],
colorCountAndIndex
[
0
]
]));
}
// update primaries
primaries
.
forEach
((
primary
,
idx
)
=>
{
const
primaryAvg
=
averagesByPrimary
.
get
(
idx
);
if
(
primaryAvg
)
{
if
(
primary
.
distance
(
primaryAvg
[
0
][
0
],
primaryAvg
[
0
][
1
],
primaryAvg
[
0
][
2
])
>
1
)
{
clusterChanges
++
;
}
primary
.
v
[
0
]
=
Math
.
round
(
primaryAvg
[
0
][
0
]);
primary
.
v
[
1
]
=
Math
.
round
(
primaryAvg
[
0
][
1
]);
primary
.
v
[
2
]
=
Math
.
round
(
primaryAvg
[
0
][
2
]);
}
});
console
.
log
(
`
${
clusterChanges
}
cluster changes,
${
colorChanges
}
colors changed cluster.`
);
// , averagesByPrimary);
// Remove tiny clusters
const
clustersWithValues
=
[];
for
(
const
averageByPrimary
of
averagesByPrimary
.
values
())
{
if
(
averageByPrimary
[
1
]
>
0
)
{
clustersWithValues
.
push
(
averageByPrimary
);
}
}
if
(
true
||
clustersWithValues
.
length
<
averagesByPrimary
.
size
)
{
const
clusterPowers
=
clustersWithValues
.
reduce
((
sum
,
averageByPrimary
)
=>
sum
+
averageByPrimary
[
1
],
0
);
const
clusterPowerAvg
=
clusterPowers
/
clustersWithValues
.
length
;
console
.
log
(
`Cluster power avg=
${
clusterPowerAvg
}
clustersWithValues
${
clustersWithValues
.
length
}
`
);
const
sortedClusters
=
clustersWithValues
.
sort
((
averageByPrimary1
,
averageByPrimary2
)
=>
Vector
.
fromArray
(
averageByPrimary1
[
0
]).
length
()
-
Vector
.
fromArray
(
averageByPrimary2
[
0
]).
length
());
// console.log(`Distance sorted ${sortedClusters.length} clusters`, sortedClusters);
let
toRemove
=
[];
let
prevAbP
=
sortedClusters
[
0
];
let
prevAbPv
=
Vector
.
fromArray
(
prevAbP
[
0
]);
for
(
let
i
=
1
;
i
<
sortedClusters
.
length
;
i
++
)
{
const
AbP
=
sortedClusters
[
i
];
// console.log('Comparing', prevAbP, AbP);
// Distance to prev
const
distance
=
prevAbPv
.
distance
(
AbP
[
0
][
0
],
AbP
[
0
][
1
],
AbP
[
0
][
2
]);
// console.log(`Distance ${prevAbPv.toString()}`, distance);
if
(
distance
<
20
)
{
if
(
AbP
[
1
]
>
clusterPowerAvg
&&
prevAbP
[
1
]
>
clusterPowerAvg
)
{
// keep both
}
else
if
(
AbP
[
1
]
<
clusterPowerAvg
)
{
toRemove
.
push
(
i
);
}
else
if
(
prevAbP
[
1
]
<
clusterPowerAvg
)
{
toRemove
.
push
(
i
-
1
);
}
}
prevAbP
=
AbP
;
prevAbPv
=
Vector
.
fromArray
(
AbP
[
0
]);
}
// console.log('We should remove', toRemove);
toRemove
=
toRemove
.
filter
((
v
,
idx
,
arr
)
=>
arr
.
indexOf
(
v
)
===
idx
);
if
(
toRemove
.
length
>
0
)
{
console
.
log
(
'
We will remove
'
,
toRemove
);
const
keeping
=
sortedClusters
.
filter
((
AbP
,
idx
)
=>
toRemove
.
indexOf
(
idx
)
<
0
);
console
.
log
(
`Keeping
${
keeping
.
length
}
`
,
keeping
);
console
.
log
(
`averagesByPrimary1
${
averagesByPrimary
.
size
}
`
);
// , averagesByPrimary);
averagesByPrimary
.
forEach
((
AbP
,
key
)
=>
{
if
(
keeping
.
indexOf
(
AbP
)
<
0
)
{
averagesByPrimary
.
delete
(
key
);
}
});
console
.
log
(
`averagesByPrimary2
${
averagesByPrimary
.
size
}
`
);
// , averagesByPrimary);
}
}
console
.
log
(
`Primaries1
${
primaries
.
length
}
`
);
primaries
=
primaries
.
filter
((
primary
,
idx
)
=>
averagesByPrimary
.
has
(
idx
));
console
.
log
(
`Primaries2
${
primaries
.
length
}
`
);
// primaries = primaries.filter((primary, idx) => averagesByPrimary.has(idx) && averagesByPrimary.get(idx)[1] > clusterPowerAvg / 2);
}
while
(
clusterChanges
>
0
&&
safety
++
<
10
);
// primaries = primaries.filter((primary, idx) => averagesByPrimary.has(idx));
// primaries.sort(Vector.compareByCoordinates).forEach((primary, idx) => {
// console.log(`R${safety} P#${idx} = ${primary.v[0]}, ${primary.v[1]}, ${primary.v[2]}`)
// });
for
(
let
y
=
0
;
y
<
h
;
y
++
)
{
for
(
let
x
=
0
;
x
<
w
;
x
++
)
{
const
p
=
(
y
*
w
+
x
)
*
4
;
const
R
=
data
[
p
+
0
];
const
G
=
data
[
p
+
1
];
const
B
=
data
[
p
+
2
];
// const A = data[p + 3];
const
avgColor
=
primaries
[
findPrimary
(
R
,
G
,
B
)];
dataR
[
p
+
0
]
=
avgColor
.
v
[
0
];
dataR
[
p
+
1
]
=
avgColor
.
v
[
1
];
dataR
[
p
+
2
]
=
avgColor
.
v
[
2
];
dataR
[
p
+
3
]
=
255
;
}
}
return
new
ImageData
(
dataR
,
w
,
h
);
// let topColors = new Array(buckets * buckets);
// let colorCount = 0;
// for (let r = 0; r < buckets; r++) {
// for (let g = 0; g < buckets; g++) {
// for (let b = 0; b < buckets; b++) {
// const pH = r * buckets * buckets + g * buckets + b;
// // console.log(`Color ${pH} f=${factor}`, r, g, b);
// if (histogram[pH] > 0) {
// const v = new Vector(Math.ceil(r * factor), Math.ceil(g * factor), Math.ceil(b * factor));
// topColors.push([v, histogram[pH]]);
// colorCount++;
// // console.log(`Color ${pH}`, v.toString(), histogram[pH]);
// }
// }
// }
// }
// topColors.sort((a, b) => b[1] - a[1]);
// topColors.slice(0, 50).forEach((a, idx) => {
// console.log(`#${idx + 1} ${a[0].toString()}`, a[1]);
// });
// topColors = topColors.map((a) => a[0]);
// const mainColors = topColors.splice(0, 1); // Math.ceil(colorCount * 0.01));
// console.log(`Starting with ${mainColors.length} main colors of ${topColors.length}`);
// topColors.forEach((color, idx) => {
// if (mainColors.length > 10000) {
// console.log('** BREAK **');
// return;
// }
// for (const color2 of mainColors) {
// if (Vector.distance(color, color2) <= 16) {
// // console.log(`Distance ${color.toString()}:${color2.toString()} is`, Vector.distance(color, color2));
// mainColors.push(color);
// topColors.splice(idx, 1);
// // console.log(`Removed ${removed.toString()}`);
// return;
// }
// }
// });
// console.log(`Clear Alpha for top ${mainColors.length} colors of ${colorCount}`);
// mainColors.forEach((color) => {
// let clearedPixels = 0;
// for (let y = 0; y < h; y++) {
// for (let x = 0; x < w; x++) {
// const p = (y * w + x) * 4;
// if (dataR[p + 0] === color.v[0] && dataR[p + 1] === color.v[1] && dataR[p + 2] === color.v[2]) {
// dataR[p + 3] = 0;
// clearedPixels++;
// }
// }
// }
// console.log(`Top color ${color.toString()} cleared ${clearedPixels} pixels.`);
// });
// return new ImageData(dataR, w, h);
}
/**
* Implementation of filtering by kernel
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment