feat: add contrast/sharpen strength controls, disable binarization
Major improvements to preprocessing controls: Backend: - Add contrast_strength (0.5-3.0) and sharpen_strength (0.5-2.0) to PreprocessingConfig - Auto-detection now calculates optimal strength based on image quality metrics: - Lower contrast → Higher contrast_strength - Lower edge strength → Higher sharpen_strength - Disable binarization in auto mode (rarely beneficial) - CLAHE clipLimit now scales with contrast_strength - Sharpening uses unsharp mask with variable strength Frontend: - Add strength sliders for contrast and sharpen in manual mode - Sliders show current value and strength level (輕微/正常/強/最強) - Move binarize option to collapsible "進階選項" section - Updated i18n translations for strength labels 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,20 @@ export default function PreprocessingSettings({
|
||||
onConfigChange({ ...config, [field]: value })
|
||||
}
|
||||
|
||||
const getStrengthLabel = (value: number, type: 'contrast' | 'sharpen') => {
|
||||
if (type === 'contrast') {
|
||||
if (value <= 0.75) return t('processing.preprocessing.strength.subtle')
|
||||
if (value <= 1.25) return t('processing.preprocessing.strength.normal')
|
||||
if (value <= 2.0) return t('processing.preprocessing.strength.strong')
|
||||
return t('processing.preprocessing.strength.maximum')
|
||||
} else {
|
||||
if (value <= 0.75) return t('processing.preprocessing.strength.subtle')
|
||||
if (value <= 1.25) return t('processing.preprocessing.strength.normal')
|
||||
if (value <= 1.5) return t('processing.preprocessing.strength.strong')
|
||||
return t('processing.preprocessing.strength.maximum')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('border rounded-lg p-4 bg-white', className)}>
|
||||
{/* Header */}
|
||||
@@ -131,14 +145,14 @@ export default function PreprocessingSettings({
|
||||
|
||||
{/* Manual Configuration (shown only when mode is 'manual') */}
|
||||
{mode === 'manual' && (
|
||||
<div className="mt-4 p-3 bg-gray-50 rounded-lg border border-gray-200 space-y-3">
|
||||
<div className="mt-4 p-3 bg-gray-50 rounded-lg border border-gray-200 space-y-4">
|
||||
<h4 className="text-sm font-medium text-gray-700">
|
||||
{t('processing.preprocessing.manualConfig')}
|
||||
</h4>
|
||||
|
||||
{/* Contrast Enhancement */}
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-600 mb-1.5">
|
||||
<div className="space-y-2">
|
||||
<label className="block text-xs font-medium text-gray-600">
|
||||
{t('processing.preprocessing.contrast.label')}
|
||||
</label>
|
||||
<select
|
||||
@@ -157,38 +171,99 @@ export default function PreprocessingSettings({
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{/* Contrast Strength Slider */}
|
||||
{config.contrast !== 'none' && (
|
||||
<div className="pt-1">
|
||||
<div className="flex justify-between text-xs text-gray-500 mb-1">
|
||||
<span>{t('processing.preprocessing.strength.label')}</span>
|
||||
<span className="font-medium text-gray-700">
|
||||
{config.contrast_strength.toFixed(1)} ({getStrengthLabel(config.contrast_strength, 'contrast')})
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0.5"
|
||||
max="3.0"
|
||||
step="0.1"
|
||||
value={config.contrast_strength}
|
||||
onChange={(e) => handleConfigChange('contrast_strength', parseFloat(e.target.value))}
|
||||
disabled={disabled}
|
||||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-gray-400 mt-0.5">
|
||||
<span>0.5</span>
|
||||
<span>3.0</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sharpen Toggle */}
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.sharpen}
|
||||
onChange={(e) => handleConfigChange('sharpen', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">
|
||||
{t('processing.preprocessing.sharpen')}
|
||||
</span>
|
||||
</label>
|
||||
{/* Sharpen Section */}
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.sharpen}
|
||||
onChange={(e) => handleConfigChange('sharpen', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">
|
||||
{t('processing.preprocessing.sharpen')}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
{/* Binarize Toggle */}
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.binarize}
|
||||
onChange={(e) => handleConfigChange('binarize', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">
|
||||
{t('processing.preprocessing.binarize')}
|
||||
</span>
|
||||
<span className="text-xs text-orange-600">
|
||||
({t('processing.preprocessing.binarizeWarning')})
|
||||
</span>
|
||||
</label>
|
||||
{/* Sharpen Strength Slider */}
|
||||
{config.sharpen && (
|
||||
<div className="pl-6 pt-1">
|
||||
<div className="flex justify-between text-xs text-gray-500 mb-1">
|
||||
<span>{t('processing.preprocessing.strength.label')}</span>
|
||||
<span className="font-medium text-gray-700">
|
||||
{config.sharpen_strength.toFixed(1)} ({getStrengthLabel(config.sharpen_strength, 'sharpen')})
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0.5"
|
||||
max="2.0"
|
||||
step="0.1"
|
||||
value={config.sharpen_strength}
|
||||
onChange={(e) => handleConfigChange('sharpen_strength', parseFloat(e.target.value))}
|
||||
disabled={disabled}
|
||||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-gray-400 mt-0.5">
|
||||
<span>0.5</span>
|
||||
<span>2.0</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Binarize Toggle - Hidden by default, shown only in advanced mode */}
|
||||
<details className="pt-2">
|
||||
<summary className="text-xs text-gray-500 cursor-pointer hover:text-gray-700">
|
||||
{t('processing.preprocessing.advanced')}
|
||||
</summary>
|
||||
<div className="mt-2 pl-2 border-l-2 border-gray-200">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.binarize}
|
||||
onChange={(e) => handleConfigChange('binarize', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">
|
||||
{t('processing.preprocessing.binarize')}
|
||||
</span>
|
||||
<span className="text-xs text-orange-600">
|
||||
({t('processing.preprocessing.binarizeWarning')})
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -68,9 +68,9 @@
|
||||
"title": "影像前處理",
|
||||
"mode": {
|
||||
"auto": "自動模式",
|
||||
"autoDesc": "系統自動分析影像品質,決定最佳的前處理方式",
|
||||
"autoDesc": "系統自動分析影像品質,決定最佳的前處理方式和強度",
|
||||
"manual": "手動模式",
|
||||
"manualDesc": "手動選擇前處理選項,完全控制處理流程",
|
||||
"manualDesc": "手動選擇前處理選項和強度,完全控制處理流程",
|
||||
"disabled": "停用前處理",
|
||||
"disabledDesc": "不進行任何前處理,直接使用原始影像"
|
||||
},
|
||||
@@ -84,8 +84,16 @@
|
||||
"clahe": "CLAHE 自適應均衡化"
|
||||
},
|
||||
"sharpen": "邊緣銳化",
|
||||
"strength": {
|
||||
"label": "強度",
|
||||
"subtle": "輕微",
|
||||
"normal": "正常",
|
||||
"strong": "強",
|
||||
"maximum": "最強"
|
||||
},
|
||||
"advanced": "進階選項",
|
||||
"binarize": "二值化處理",
|
||||
"binarizeWarning": "可能影響顏色資訊",
|
||||
"binarizeWarning": "不建議使用",
|
||||
"note": "前處理僅影響版面偵測階段,用於改善表格和文字區塊的識別。原始影像仍用於最終的 OCR 文字提取,確保最佳識別品質。"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -39,7 +39,9 @@ export default function ProcessingPage() {
|
||||
const [preprocessingMode, setPreprocessingMode] = useState<PreprocessingMode>('auto')
|
||||
const [preprocessingConfig, setPreprocessingConfig] = useState<PreprocessingConfig>({
|
||||
contrast: 'clahe',
|
||||
contrast_strength: 1.0,
|
||||
sharpen: true,
|
||||
sharpen_strength: 1.0,
|
||||
binarize: false,
|
||||
})
|
||||
|
||||
|
||||
@@ -100,7 +100,9 @@ export type PreprocessingContrast = 'none' | 'histogram' | 'clahe'
|
||||
*/
|
||||
export interface PreprocessingConfig {
|
||||
contrast: PreprocessingContrast
|
||||
contrast_strength: number // 0.5-3.0, default 1.0
|
||||
sharpen: boolean
|
||||
sharpen_strength: number // 0.5-2.0, default 1.0
|
||||
binarize: boolean
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user