372 lines
13 KiB
PowerShell
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"
|