Layer 4: blind spectral watermark on the mesh Laplacian. More...
| Header: | #include <SpectralWatermark> |
| int | capacity(const stl::Mesh &mesh) const |
The class comment in the header gives the five steps. This file keeps the reasons for the choices, because each one was measured against the testWatermark distortions on the 1538-vertex test cube:
- A vertex graph is only stable if the welder rejoins cracked corners. The per-corner noise of the test (0.5 % of the diagonal, 14 % of an edge per coordinate) plus the per-corner moves of the vertex displacement layer split vertices apart. A weld with about ten wrong vertices in 1538 scrambles the coefficients: the median coefficient error went from 0.004 to 0.05 scale units, ten times the noise. The welder below gives the exact topology for 40 of 40 noise seeds at 0.4 %, 38 of 40 at 0.5 %, 26 of 40 at 0.55 % and none at 0.6 %. - Rotation invariance needs a frame, and the cube defeats a PCA frame (its inertia is isotropic). The polar factor of the coefficients of eigenvectors 1-3 does not: they are large (about 14 for the cube) and far apart, so noise turns the frame by about 2.5e-4 rad. - Blind detection needs no estimate of the host, so this uses dither modulation (QIM) and not additive spread spectrum. The old code correlated coefficient magnitudes with a ±1 sequence; the host magnitudes swamp that sum, so it could never decode.
Capacity depends on the vertex count of mesh only, through layoutFor(): the band is `min(bandSize, n - 5)` eigenvectors of three components each, `chipsPerBit` components make a bit, and the CRC and the length field come off the top. Under the defaults a mesh of 125 or more vertices carries 360 bits, which is 42 bytes of payload.