DetectionModelTraining/13_preview_roi_samples.ps1

372 lines
13 KiB
PowerShell

$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Drawing
$repo = $PSScriptRoot
$outputDir = Join-Path $repo "samples\roi_preview"
if (Test-Path $outputDir) {
Remove-Item $outputDir -Recurse -Force
}
New-Item -ItemType Directory -Path $outputDir | Out-Null
$imageExts = @(".jpg", ".jpeg", ".png", ".bmp", ".webp")
function New-Box {
param(
[double]$X1,
[double]$Y1,
[double]$X2,
[double]$Y2
)
[PSCustomObject]@{
X1 = $X1
Y1 = $Y1
X2 = $X2
Y2 = $Y2
W = [Math]::Max(0.0, $X2 - $X1)
H = [Math]::Max(0.0, $Y2 - $Y1)
Cx = ($X1 + $X2) / 2.0
Cy = ($Y1 + $Y2) / 2.0
Area = [Math]::Max(0.0, $X2 - $X1) * [Math]::Max(0.0, $Y2 - $Y1)
}
}
function Clip-Box {
param(
$Box,
[double]$Width,
[double]$Height
)
$x1 = [Math]::Min([Math]::Max($Box.X1, 0.0), $Width)
$y1 = [Math]::Min([Math]::Max($Box.Y1, 0.0), $Height)
$x2 = [Math]::Min([Math]::Max($Box.X2, 0.0), $Width)
$y2 = [Math]::Min([Math]::Max($Box.Y2, 0.0), $Height)
if ($x2 -le $x1 -or $y2 -le $y1) {
return $null
}
return New-Box -X1 $x1 -Y1 $y1 -X2 $x2 -Y2 $y2
}
function Find-Image {
param(
[string]$ImageDir,
[string]$Stem
)
foreach ($ext in $imageExts) {
$path = Join-Path $ImageDir ($Stem + $ext)
if (Test-Path $path) {
return $path
}
}
return $null
}
function Load-Boxes {
param(
[string]$LabelPath,
[int]$ImageWidth,
[int]$ImageHeight
)
$boxes = @()
foreach ($line in Get-Content $LabelPath) {
$text = $line.Trim()
if (-not $text) {
continue
}
$parts = $text -split "\s+"
if ($parts.Length -lt 5) {
continue
}
$xc = [double]$parts[1] * $ImageWidth
$yc = [double]$parts[2] * $ImageHeight
$w = [double]$parts[3] * $ImageWidth
$h = [double]$parts[4] * $ImageHeight
$box = New-Box -X1 ($xc - $w / 2.0) -Y1 ($yc - $h / 2.0) -X2 ($xc + $w / 2.0) -Y2 ($yc + $h / 2.0)
$clipped = Clip-Box -Box $box -Width $ImageWidth -Height $ImageHeight
if ($null -ne $clipped -and $clipped.Area -gt 1.0) {
$boxes += $clipped
}
}
return $boxes | Sort-Object Cx, Cy
}
function Should-Pair {
param($Left, $Right)
$widthRef = [Math]::Max($Left.W, $Right.W)
$heightRef = [Math]::Max($Left.H, $Right.H)
if ($widthRef -le 0 -or $heightRef -le 0) {
return $false
}
$dx = [Math]::Abs($Left.Cx - $Right.Cx)
$dy = [Math]::Abs($Left.Cy - $Right.Cy)
$areaRatio = if ($Right.Area -gt 0) { $Left.Area / $Right.Area } else { [double]::PositiveInfinity }
return (
$dx -le ($widthRef * 3.2) -and
$dy -le ($heightRef * 1.2) -and
$areaRatio -ge 0.4 -and
$areaRatio -le 2.5
)
}
function Get-Groups {
param($Boxes)
$groups = @()
$used = @{}
$candidates = @()
for ($i = 0; $i -lt $Boxes.Count; $i++) {
for ($j = $i + 1; $j -lt $Boxes.Count; $j++) {
if (Should-Pair $Boxes[$i] $Boxes[$j]) {
$score = [Math]::Abs($Boxes[$i].Cx - $Boxes[$j].Cx) + 0.5 * [Math]::Abs($Boxes[$i].Cy - $Boxes[$j].Cy)
$candidates += [PSCustomObject]@{ Score = $score; I = $i; J = $j }
}
}
}
foreach ($candidate in ($candidates | Sort-Object Score)) {
if ($used.ContainsKey($candidate.I) -or $used.ContainsKey($candidate.J)) {
continue
}
$used[$candidate.I] = $true
$used[$candidate.J] = $true
$groups += ,@($candidate.I, $candidate.J)
}
for ($i = 0; $i -lt $Boxes.Count; $i++) {
if (-not $used.ContainsKey($i)) {
$groups += ,@($i)
}
}
return $groups
}
function Estimate-PersonFromSingle {
param($Box)
$personW = [Math]::Max(4.2 * $Box.W, 2.8 * $Box.H)
$personH = [Math]::Max(7.6 * $Box.H, 3.4 * $Box.W)
$personCx = $Box.Cx
$personY2 = $Box.Y2 + 0.08 * $personH
$personX1 = $personCx - $personW / 2.0
$personY1 = $personY2 - $personH
return New-Box -X1 $personX1 -Y1 $personY1 -X2 ($personX1 + $personW) -Y2 $personY2
}
function Estimate-PersonFromPair {
param($A, $B)
$ux1 = [Math]::Min($A.X1, $B.X1)
$uy1 = [Math]::Min($A.Y1, $B.Y1)
$ux2 = [Math]::Max($A.X2, $B.X2)
$uy2 = [Math]::Max($A.Y2, $B.Y2)
$uw = $ux2 - $ux1
$uh = $uy2 - $uy1
$personW = [Math]::Max(1.95 * $uw, 2.6 * $uh)
$personH = [Math]::Max(7.8 * $uh, 2.9 * $uw)
$personCx = ($ux1 + $ux2) / 2.0
$personY2 = $uy2 + 0.08 * $personH
$personX1 = $personCx - $personW / 2.0
$personY1 = $personY2 - $personH
return New-Box -X1 $personX1 -Y1 $personY1 -X2 ($personX1 + $personW) -Y2 $personY2
}
function Get-RoiFromPerson {
param($Person)
$roiX = $Person.X1 - 0.24 * $Person.W
$roiY = $Person.Y1 + 0.64 * $Person.H
$roiW = 1.48 * $Person.W
$roiH = 0.58 * $Person.H
$roiX = $roiX - 0.08 * $roiW
$roiY = $roiY - 0.08 * $roiH
$roiW = $roiW * 1.16
$roiH = $roiH * 1.18
return New-Box -X1 $roiX -Y1 $roiY -X2 ($roiX + $roiW) -Y2 ($roiY + $roiH)
}
function Clamp-Roi {
param(
$Roi,
[int]$ImageWidth,
[int]$ImageHeight
)
$clipped = Clip-Box -Box $Roi -Width $ImageWidth -Height $ImageHeight
if ($null -eq $clipped) {
return $null
}
$x1 = [Math]::Max(0, [Math]::Min([int][Math]::Floor($clipped.X1), $ImageWidth - 1))
$y1 = [Math]::Max(0, [Math]::Min([int][Math]::Floor($clipped.Y1), $ImageHeight - 1))
$x2 = [Math]::Max($x1 + 1, [Math]::Min([int][Math]::Ceiling($clipped.X2), $ImageWidth))
$y2 = [Math]::Max($y1 + 1, [Math]::Min([int][Math]::Ceiling($clipped.Y2), $ImageHeight))
return New-Box -X1 $x1 -Y1 $y1 -X2 $x2 -Y2 $y2
}
function Resize-RoiToTarget {
param(
$Roi,
[int]$ImageWidth,
[int]$ImageHeight,
[double]$ObjectArea,
[double]$MinRatio,
[double]$MaxRatio
)
$adjusted = $Roi
$targetRatio = ($MinRatio + $MaxRatio) / 2.0
for ($iter = 0; $iter -lt 3; $iter++) {
$ratio = if ($adjusted.Area -gt 0) { $ObjectArea / $adjusted.Area } else { 0.0 }
if ($ratio -ge $MinRatio -and $ratio -le $MaxRatio) {
return $adjusted
}
$scale = [Math]::Sqrt($ratio / $targetRatio)
if ($ratio -lt $MinRatio) {
$scale = [Math]::Max(0.6, [Math]::Min(0.95, $scale))
}
else {
$scale = [Math]::Min(1.8, [Math]::Max(1.05, $scale))
}
$newW = $adjusted.W * $scale
$newH = $adjusted.H * $scale
$cx = $adjusted.Cx
$cy = $adjusted.Cy
$adjusted = Clamp-Roi (New-Box -X1 ($cx - $newW / 2.0) -Y1 ($cy - $newH / 2.0) -X2 ($cx + $newW / 2.0) -Y2 ($cy + $newH / 2.0)) $ImageWidth $ImageHeight
if ($null -eq $adjusted) {
return $null
}
}
return $adjusted
}
function Draw-Rect {
param(
[System.Drawing.Graphics]$Graphics,
[System.Drawing.Pen]$Pen,
$Box
)
$Graphics.DrawRectangle($Pen, [float]$Box.X1, [float]$Box.Y1, [float]$Box.W, [float]$Box.H)
}
$samples = @(
@{ Dataset = "datasets\openimages-shoes-yolo"; Split = "train"; Stem = "00015a7cf95ec19d"; Label = "openimages_single" },
@{ Dataset = "datasets\openimages-shoes-yolo"; Split = "train"; Stem = "0036655159bdef7f"; Label = "openimages_pair" },
@{ Dataset = "datasets\openimages-shoes-yolo"; Split = "train"; Stem = "00003223e04e2e66"; Label = "openimages_mixed" },
@{ Dataset = "datasets\ppe-shoes"; Split = "train"; Stem = "image1001"; Label = "ppe_pair_a" },
@{ Dataset = "datasets\ppe-shoes"; Split = "train"; Stem = "image1011"; Label = "ppe_pair_b" }
)
$summary = @()
foreach ($sample in $samples) {
$imageDir = Join-Path $repo (Join-Path $sample.Dataset ("images\" + $sample.Split))
$labelPath = Join-Path $repo (Join-Path $sample.Dataset ("labels\" + $sample.Split + "\" + $sample.Stem + ".txt"))
$imagePath = Find-Image -ImageDir $imageDir -Stem $sample.Stem
if (-not $imagePath -or -not (Test-Path $labelPath)) {
continue
}
$bitmap = [System.Drawing.Bitmap]::new($imagePath)
try {
$boxes = Load-Boxes -LabelPath $labelPath -ImageWidth $bitmap.Width -ImageHeight $bitmap.Height
if ($boxes.Count -eq 0) {
continue
}
$groups = Get-Groups -Boxes $boxes
$canvas = [System.Drawing.Bitmap]::new($bitmap)
$graphics = [System.Drawing.Graphics]::FromImage($canvas)
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
$shoePen = [System.Drawing.Pen]::new([System.Drawing.Color]::Lime, 3)
$roiPen = [System.Drawing.Pen]::new([System.Drawing.Color]::Red, 4)
$font = [System.Drawing.Font]::new("Arial", 18, [System.Drawing.FontStyle]::Bold)
$brush = [System.Drawing.SolidBrush]::new([System.Drawing.Color]::Yellow)
try {
for ($i = 0; $i -lt $boxes.Count; $i++) {
Draw-Rect -Graphics $graphics -Pen $shoePen -Box $boxes[$i]
}
$roiIndex = 0
foreach ($group in $groups) {
if ($group.Count -eq 2) {
$person = Estimate-PersonFromPair $boxes[$group[0]] $boxes[$group[1]]
$roi = Get-RoiFromPerson $person
$minRatio = 0.18
$maxRatio = 0.40
$mode = "pair"
}
else {
$person = Estimate-PersonFromSingle $boxes[$group[0]]
$roi = Get-RoiFromPerson $person
$minRatio = 0.10
$maxRatio = 0.26
$mode = "single"
}
$roi = Clamp-Roi $roi $bitmap.Width $bitmap.Height
if ($null -eq $roi) {
continue
}
$objectArea = 0.0
foreach ($idx in $group) {
$objectArea += $boxes[$idx].Area
}
$roi = Resize-RoiToTarget $roi $bitmap.Width $bitmap.Height $objectArea $minRatio $maxRatio
if ($null -eq $roi) {
continue
}
Draw-Rect -Graphics $graphics -Pen $roiPen -Box $roi
$graphics.DrawString("ROI$roiIndex", $font, $brush, [float]($roi.X1 + 4), [float]([Math]::Max(0, $roi.Y1 - 28)))
$cropRect = [System.Drawing.Rectangle]::new([int]$roi.X1, [int]$roi.Y1, [int]$roi.W, [int]$roi.H)
$crop = $bitmap.Clone($cropRect, $bitmap.PixelFormat)
$cropGraphics = [System.Drawing.Graphics]::FromImage($crop)
$cropPen = [System.Drawing.Pen]::new([System.Drawing.Color]::Lime, 3)
try {
foreach ($box in $boxes) {
if ($box.Cx -lt $roi.X1 -or $box.Cx -gt $roi.X2 -or $box.Cy -lt $roi.Y1 -or $box.Cy -gt $roi.Y2) {
continue
}
$local = New-Box -X1 ($box.X1 - $roi.X1) -Y1 ($box.Y1 - $roi.Y1) -X2 ($box.X2 - $roi.X1) -Y2 ($box.Y2 - $roi.Y1)
$local = Clip-Box -Box $local -Width $roi.W -Height $roi.H
if ($null -ne $local) {
Draw-Rect -Graphics $cropGraphics -Pen $cropPen -Box $local
}
}
}
finally {
$cropGraphics.Dispose()
$cropPen.Dispose()
}
$cropPath = Join-Path $outputDir ($sample.Label + "_roi" + $roiIndex + ".jpg")
$crop.Save($cropPath, [System.Drawing.Imaging.ImageFormat]::Jpeg)
$crop.Dispose()
$areaRatio = [Math]::Round($objectArea / $roi.Area, 3)
$summary += [PSCustomObject]@{
Sample = $sample.Label
Roi = "roi$roiIndex"
Mode = $mode
Boxes = $group.Count
AreaRatio = $areaRatio
Crop = $cropPath
}
$roiIndex++
}
$overlayPath = Join-Path $outputDir ($sample.Label + "_overlay.jpg")
$canvas.Save($overlayPath, [System.Drawing.Imaging.ImageFormat]::Jpeg)
}
finally {
$graphics.Dispose()
$shoePen.Dispose()
$roiPen.Dispose()
$font.Dispose()
$brush.Dispose()
$canvas.Dispose()
}
}
finally {
$bitmap.Dispose()
}
}
$summaryPath = Join-Path $outputDir "summary.txt"
$summary | Sort-Object Sample, Roi | Format-Table -AutoSize | Out-String | Set-Content -Path $summaryPath -Encoding UTF8
Write-Output "Preview images written to: $outputDir"
Write-Output "Summary written to: $summaryPath"