-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add CLAHE to opencv image tool #8730
base: develop
Are you sure you want to change the base?
Changes from 4 commits
d8c8c6d
d189486
f2adbd2
5018b84
437351b
1d4e294
add6ed4
0833218
866900f
e15f7b6
57ecbe4
2053b10
5d13020
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
### Added | ||
|
||
- Added contrast limited adaptive histogram equalization tool | ||
(<https://github.com/cvat-ai/cvat/pull/8730>) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,10 +7,11 @@ import React from 'react'; | |
import { connect } from 'react-redux'; | ||
import { Row, Col } from 'antd/lib/grid'; | ||
import Popover from 'antd/lib/popover'; | ||
import Icon, { AreaChartOutlined, ScissorOutlined } from '@ant-design/icons'; | ||
import Icon, { AreaChartOutlined, BarChartOutlined, ScissorOutlined } from '@ant-design/icons'; | ||
import Text from 'antd/lib/typography/Text'; | ||
import Tabs from 'antd/lib/tabs'; | ||
import Button from 'antd/lib/button'; | ||
import InputNumber from 'antd/lib/input-number'; | ||
import Progress from 'antd/lib/progress'; | ||
import Select from 'antd/lib/select'; | ||
import notification from 'antd/lib/notification'; | ||
|
@@ -84,6 +85,9 @@ interface State { | |
activeTracker: OpenCVTracker | null; | ||
trackers: OpenCVTracker[]; | ||
lastTrackedFrame: number | null; | ||
claheClipLimit: number; | ||
claheTileColumns: number; | ||
claheTileRows: number; | ||
} | ||
|
||
const core = getCore(); | ||
|
@@ -156,6 +160,9 @@ class OpenCVControlComponent extends React.PureComponent<Props & DispatchToProps | |
trackers: openCVWrapper.isInitialized ? Object.values(openCVWrapper.tracking) : [], | ||
activeTracker: openCVWrapper.isInitialized ? Object.values(openCVWrapper.tracking)[0] : null, | ||
lastTrackedFrame: null, | ||
claheClipLimit: 40, | ||
claheTileColumns: 8, | ||
claheTileRows: 8, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I noticed that the constants are duplicated here and in clahe.ts. Would it be possible to import the defaults directly from clahe.ts to avoid redundancy? |
||
}; | ||
} | ||
|
||
|
@@ -581,6 +588,8 @@ class OpenCVControlComponent extends React.PureComponent<Props & DispatchToProps | |
|
||
private renderImageContent():JSX.Element { | ||
const { enableImageFilter, disableImageFilter, filters } = this.props; | ||
const { claheClipLimit, claheTileColumns, claheTileRows } = this.state; | ||
|
||
return ( | ||
<Row justify='start'> | ||
<Col> | ||
|
@@ -607,6 +616,118 @@ class OpenCVControlComponent extends React.PureComponent<Props & DispatchToProps | |
</Button> | ||
</CVATTooltip> | ||
</Col> | ||
<Col> | ||
<CVATTooltip title='Contrast Limited Adaptive Histogram Equalization' className='cvat-opencv-image-tool'> | ||
<Button | ||
className={ | ||
hasFilter(filters, ImageFilterAlias.CLAHE) ? | ||
'cvat-opencv-clahe-tool-button cvat-opencv-image-tool-active' : 'cvat-opencv-clahe-tool-button' | ||
} | ||
onClick={(e: React.MouseEvent<HTMLElement>) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code for enabling the filter (and for the histogram as well) could be extracted into a separate function to reduce duplication |
||
if (!hasFilter(filters, ImageFilterAlias.CLAHE)) { | ||
enableImageFilter({ | ||
modifier: openCVWrapper.imgproc.clahe(), | ||
alias: ImageFilterAlias.CLAHE, | ||
}, | ||
{ | ||
clipLimit: claheClipLimit, | ||
tileGridSize: { | ||
columns: claheTileColumns, | ||
rows: claheTileRows, | ||
}, | ||
}); | ||
} else { | ||
const button = e.target as HTMLElement; | ||
button.blur(); | ||
disableImageFilter(ImageFilterAlias.CLAHE); | ||
} | ||
}} | ||
klakhov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
> | ||
<BarChartOutlined /> | ||
</Button> | ||
</CVATTooltip> | ||
|
||
{hasFilter(filters, ImageFilterAlias.CLAHE) && ( | ||
<div> | ||
<Row align='middle' gutter={8}> | ||
<Col span={12}>Clip Limit:</Col> | ||
<Col span={12}> | ||
<InputNumber | ||
min={1} | ||
max={255} | ||
step={1} | ||
value={claheClipLimit} | ||
onChange={(value) => { | ||
this.setState({ | ||
claheClipLimit: value || 40, | ||
}); | ||
enableImageFilter({ | ||
modifier: openCVWrapper.imgproc.clahe(), | ||
alias: ImageFilterAlias.CLAHE, | ||
}, { | ||
clipLimit: value || 40, | ||
tileGridSize: { | ||
columns: claheTileColumns, | ||
rows: claheTileRows, | ||
}, | ||
}); | ||
}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Reduce code duplication in parameter update handlers The parameter update logic is repeated across multiple onChange handlers. Consider extracting this into a shared method. + private updateCLAHEParameters(
+ paramName: 'claheClipLimit' | 'claheTileColumns' | 'claheTileRows',
+ value: number
+ ): void {
+ const defaultValues = {
+ claheClipLimit: 40,
+ claheTileColumns: 8,
+ claheTileRows: 8
+ };
+
+ this.setState({
+ [paramName]: value || defaultValues[paramName]
+ });
+
+ this.enableImageFilter({
+ modifier: openCVWrapper.imgproc.clahe(),
+ alias: ImageFilterAlias.CLAHE,
+ }, {
+ clipLimit: paramName === 'claheClipLimit' ? value : this.state.claheClipLimit,
+ tileGridSize: {
+ columns: paramName === 'claheTileColumns' ? value : this.state.claheTileColumns,
+ rows: paramName === 'claheTileRows' ? value : this.state.claheTileRows,
+ },
+ });
+ }
- onChange={(value) => {
- this.setState({
- claheClipLimit: value || 40,
- });
- enableImageFilter({
- modifier: openCVWrapper.imgproc.clahe(),
- alias: ImageFilterAlias.CLAHE,
- }, {
- clipLimit: value || 40,
- tileGridSize: {
- columns: claheTileColumns,
- rows: claheTileRows,
- },
- });
- }}
+ onChange={(value) => this.updateCLAHEParameters('claheClipLimit', value)} Also applies to: 685-699, 710-724 |
||
/> | ||
</Col> | ||
</Row> | ||
<Row align='middle' gutter={8}> | ||
<Col span={12}>Tile Rows:</Col> | ||
<Col span={12}> | ||
<InputNumber | ||
min={1} | ||
max={128} | ||
value={claheTileColumns} | ||
onChange={(value) => { | ||
this.setState({ | ||
claheTileColumns: value || 8, | ||
}); | ||
enableImageFilter({ | ||
modifier: openCVWrapper.imgproc.clahe(), | ||
alias: ImageFilterAlias.CLAHE, | ||
}, { | ||
clipLimit: claheClipLimit, | ||
tileGridSize: { | ||
columns: value || 8, | ||
rows: claheTileRows, | ||
}, | ||
}); | ||
}} | ||
/> | ||
</Col> | ||
</Row> | ||
<Row align='middle' gutter={8}> | ||
<Col span={12}>Tile Columns:</Col> | ||
<Col span={12}> | ||
<InputNumber | ||
min={1} | ||
max={128} | ||
value={claheTileRows} | ||
onChange={(value) => { | ||
this.setState({ | ||
claheTileRows: value || 8, | ||
}); | ||
enableImageFilter({ | ||
modifier: openCVWrapper.imgproc.clahe(), | ||
alias: ImageFilterAlias.CLAHE, | ||
}, { | ||
clipLimit: claheClipLimit, | ||
tileGridSize: { | ||
columns: claheTileColumns, | ||
rows: value || 8, | ||
}, | ||
}); | ||
}} | ||
/> | ||
</Col> | ||
</Row> | ||
</div> | ||
)} | ||
</Col> | ||
</Row> | ||
); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ export interface ImageFilter { | |
|
||
export enum ImageFilterAlias { | ||
HISTOGRAM_EQUALIZATION = 'opencv.histogramEqualizaton', | ||
CLAHE = 'opencv.CLAHE', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Based on the search results, I can now provide a final response: The CLAHE implementation is properly integrated but lacks test coverage The verification shows:
Action items:
🔗 Analysis chainLGTM! Verify consistent usage across the codebase. The new CLAHE enum value follows the established naming and value format conventions. Let's verify the consistent usage of this new filter alias: Let me gather more information about the CLAHE implementation to verify its usage. 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check for consistent usage of the CLAHE filter alias
# Expected: Should find references in OpenCV control component and tests
# Search for CLAHE usage
rg -l "opencv\.CLAHE"
# Search for potential test files
fd -e test.tsx -e test.ts -e spec.tsx -e spec.ts
Length of output: 113 Script: #!/bin/bash
# Search for CLAHE-related code in the codebase
rg -g '!*.test.*' -g '!*.spec.*' "CLAHE" -A 5 -B 5
# Look for image processing related components
fd -e tsx -e ts | rg -i "image|filter|opencv"
# Check for any test files containing image processing related terms
fd -e test.tsx -e test.ts -e spec.tsx -e spec.ts | rg -i "image|filter|opencv"
Length of output: 19892 |
||
GAMMA_CORRECTION = 'fabric.gammaCorrection', | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,81 @@ | ||||||||||
// Copyright (C) 2021-2022 Intel Corporation | ||||||||||
// Copyright (C) 2023 CVAT.ai Corporation | ||||||||||
// | ||||||||||
// SPDX-License-Identifier: MIT | ||||||||||
|
||||||||||
import { BaseImageFilter, ImageProcessing } from 'utils/image-processing'; | ||||||||||
|
||||||||||
export interface CLAHE extends ImageProcessing { | ||||||||||
processImage: (src: ImageData, frameNumber: number) => ImageData; | ||||||||||
} | ||||||||||
|
||||||||||
export default class CLAHEImplementation extends BaseImageFilter { | ||||||||||
private cv:any; | ||||||||||
// same defaults as opencv | ||||||||||
private clipLimit: number = 40.0; | ||||||||||
private tileGridSize: { rows: number, columns: number } = { rows: 8, columns: 8 }; | ||||||||||
|
||||||||||
constructor(cv:any) { | ||||||||||
super(); | ||||||||||
this.cv = cv; | ||||||||||
} | ||||||||||
|
||||||||||
public configure(options: { | ||||||||||
clipLimit?: number, | ||||||||||
tileGridSize?: { width: number, height: number } | ||||||||||
}): void { | ||||||||||
if (options.clipLimit !== undefined) { | ||||||||||
this.clipLimit = options.clipLimit; | ||||||||||
} | ||||||||||
if (options.tileGridSize !== undefined) { | ||||||||||
this.tileGridSize = options.tileGridSize; | ||||||||||
} | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix mismatch in In the Apply this diff to align the property names: public configure(options: {
clipLimit?: number,
- tileGridSize?: { width: number, height: number }
+ tileGridSize?: { rows: number, columns: number }
}): void {
if (options.clipLimit !== undefined) {
this.clipLimit = options.clipLimit;
}
if (options.tileGridSize !== undefined) {
- this.tileGridSize = options.tileGridSize;
+ this.tileGridSize = options.tileGridSize;
}
} Alternatively, if you prefer to keep public configure(options: {
clipLimit?: number,
tileGridSize?: { width: number, height: number }
}): void {
if (options.clipLimit !== undefined) {
this.clipLimit = options.clipLimit;
}
if (options.tileGridSize !== undefined) {
this.tileGridSize = {
+ rows: options.tileGridSize.height,
+ columns: options.tileGridSize.width,
};
}
}
|
||||||||||
} | ||||||||||
|
||||||||||
public processImage(src:ImageData, frameNumber: number) : ImageData { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Much of the code here is duplicated from |
||||||||||
const { cv } = this; | ||||||||||
let matImage = null; | ||||||||||
const RGBImage = new cv.Mat(); | ||||||||||
const YUVImage = new cv.Mat(); | ||||||||||
const RGBDist = new cv.Mat(); | ||||||||||
const YUVDist = new cv.Mat(); | ||||||||||
const RGBADist = new cv.Mat(); | ||||||||||
let channels = new cv.MatVector(); | ||||||||||
const equalizedY = new cv.Mat(); | ||||||||||
const tileGridSize = new cv.Size(this.tileGridSize.rows, this.tileGridSize.columns); | ||||||||||
const clahe = new cv.CLAHE(this.clipLimit, tileGridSize); | ||||||||||
try { | ||||||||||
this.currentProcessedImage = frameNumber; | ||||||||||
matImage = cv.matFromImageData(src); | ||||||||||
cv.cvtColor(matImage, RGBImage, cv.COLOR_RGBA2RGB, 0); | ||||||||||
cv.cvtColor(RGBImage, YUVImage, cv.COLOR_RGB2YUV, 0); | ||||||||||
cv.split(YUVImage, channels); | ||||||||||
const [Y, U, V] = [channels.get(0), channels.get(1), channels.get(2)]; | ||||||||||
channels.delete(); | ||||||||||
channels = null; | ||||||||||
clahe.apply(Y, equalizedY); | ||||||||||
clahe.delete(); | ||||||||||
Y.delete(); | ||||||||||
channels = new cv.MatVector(); | ||||||||||
channels.push_back(equalizedY); equalizedY.delete(); | ||||||||||
channels.push_back(U); U.delete(); | ||||||||||
channels.push_back(V); V.delete(); | ||||||||||
cv.merge(channels, YUVDist); | ||||||||||
cv.cvtColor(YUVDist, RGBDist, cv.COLOR_YUV2RGB, 0); | ||||||||||
cv.cvtColor(RGBDist, RGBADist, cv.COLOR_RGB2RGBA, 0); | ||||||||||
const arr = new Uint8ClampedArray(RGBADist.data, RGBADist.cols, RGBADist.rows); | ||||||||||
const imgData = new ImageData(arr, src.width, src.height); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct the construction of The Apply this fix: - const arr = new Uint8ClampedArray(RGBADist.data, RGBADist.cols, RGBADist.rows);
+ const arr = new Uint8ClampedArray(RGBADist.data); This ensures that the array correctly represents the pixel data for the 📝 Committable suggestion
Suggested change
|
||||||||||
return imgData; | ||||||||||
} catch (e) { | ||||||||||
throw new Error(e.toString()); | ||||||||||
} finally { | ||||||||||
if (matImage) matImage.delete(); | ||||||||||
if (channels) channels.delete(); | ||||||||||
RGBImage.delete(); | ||||||||||
YUVImage.delete(); | ||||||||||
RGBDist.delete(); | ||||||||||
YUVDist.delete(); | ||||||||||
RGBADist.delete(); | ||||||||||
} | ||||||||||
} | ||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -31,7 +31,9 @@ See: | |||||||||||||
- [OpenCV: annotate with trackers](#opencv-annotate-with-trackers) | ||||||||||||||
- [When tracking](#when-tracking) | ||||||||||||||
- [Trackers models](#trackers-models) | ||||||||||||||
- [OpenCV: histogram equalization](#opencv-histogram-equalization) | ||||||||||||||
- [OpenCV](#opencv) | ||||||||||||||
- [Histogram Equalization](#opencv-histogram-equalization) | ||||||||||||||
- [Contrast Limited Adaptive Histogram Equalization](#opencv-clahe) | ||||||||||||||
Comment on lines
+34
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix anchor tags in TOC to match section IDs The anchor tags in the TOC links don't match the actual section IDs. This will cause broken navigation. Apply this diff to fix the anchor tags: - - [OpenCV](#opencv)
- - [Histogram Equalization](#opencv-histogram-equalization)
- - [Contrast Limited Adaptive Histogram Equalization](#opencv-clahe)
+ - [OpenCV](#opencv-1)
+ - [Histogram Equalization](#histogram-equalization)
+ - [Contrast Limited Adaptive Histogram Equalization](#contrast-limited-adaptive-histogram-equalization) 📝 Committable suggestion
Suggested change
|
||||||||||||||
|
||||||||||||||
## Interactors | ||||||||||||||
|
||||||||||||||
|
@@ -280,7 +282,8 @@ All annotated objects will be automatically tracked when you move to the next fr | |||||||||||||
|
||||||||||||||
<!--lint enable maximum-line-length--> | ||||||||||||||
|
||||||||||||||
## OpenCV: histogram equalization | ||||||||||||||
## OpenCV | ||||||||||||||
### Histogram Equalization | ||||||||||||||
|
||||||||||||||
**Histogram equalization** improves | ||||||||||||||
the contrast by stretching the intensity range. | ||||||||||||||
|
@@ -306,3 +309,15 @@ Example of the result: | |||||||||||||
![](/images/image222.jpg) | ||||||||||||||
|
||||||||||||||
To disable **Histogram equalization**, click on the button again. | ||||||||||||||
|
||||||||||||||
### Contrast Limited Adaptive Histogram Equalization | ||||||||||||||
Contrast Limited Adaptive Histogram Equalization (CLAHE) increases contrast by applying clipped histogram equalization to multiple tiles across the input image. In images where there are both very bright and very dark regions, this improves contrast in the dark regions without loosing contrast in the bright ones. | ||||||||||||||
|
||||||||||||||
#### Parameters | ||||||||||||||
* Clip Limit: Maximum value a pixel can be adjusted. Higher values allow for noise to be over-amplified. | ||||||||||||||
* Tile Rows: How many rows of tiles to break the image into. | ||||||||||||||
* Tile Columns: How many columns of tiles to break the image into. | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
User Interface | ||||||||||||||
![](/images/opencv-image-clahe_interaction.jpg) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance CLAHE documentation for better user guidance While the section provides a good overview, it needs several improvements to match the quality and format of the Histogram Equalization section:
Consider applying these improvements: ### Contrast Limited Adaptive Histogram Equalization
Contrast Limited Adaptive Histogram Equalization (CLAHE) increases contrast by applying clipped histogram equalization to multiple tiles across the input image. In images where there are both very bright and very dark regions, this improves contrast in the dark regions without loosing contrast in the bright ones.
+ To apply CLAHE to your image:
+
+ 1. In the **OpenCV** menu, go to the **Image** tab
+ 2. Click on the **CLAHE** button
+ 3. Adjust the parameters as needed (see below)
+
#### Parameters
- * Clip Limit: Maximum value a pixel can be adjusted. Higher values allow for noise to be over-amplified.
- * Tile Rows: How many rows of tiles to break the image into.
- * Tile Columns: How many columns of tiles to break the image into.
+ * **Clip Limit** (Range: 1-10): Maximum value for contrast enhancement.
+ - Lower values (1-3) prevent noise amplification but provide subtle enhancement
+ - Higher values (4-10) give stronger enhancement but may amplify noise
+ * **Tile Rows** (Range: 2-16): Number of rows to divide the image into.
+ - More rows allow for more localized enhancement
+ - Recommended: Start with 8 rows and adjust based on image size
+ * **Tile Columns** (Range: 2-16): Number of columns to divide the image into.
+ - More columns allow for more localized enhancement
+ - Recommended: Start with 8 columns and adjust based on image size
-User Interface
+#### User Interface
+The CLAHE interface provides sliders to adjust each parameter in real-time:
![](/images/opencv-image-clahe_interaction.jpg)
+#### Example Results
+Below is an example showing the effect of CLAHE:
+![Before and After CLAHE Example](/images/opencv-image-clahe-example.jpg)
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets create separate state object
filterSettings
where we can store all filter settings here.